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 1435)
+++ trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/HTMLLogParser.java	(revision 1496)
@@ -19,6 +19,6 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -35,4 +35,5 @@
 import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
 import de.ugoe.cs.autoquest.plugin.html.eventcore.HTMLEventTypeFactory;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
 import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocumentSpec;
 import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
@@ -48,5 +49,5 @@
  * </p>
  * <p>
- * The parser can be configured with parsing parameters to ignore, e.g., ids or indexes of
+ * The parser can be configured with parsing parameters to adapt, e.g., ids or or ignore indexes of
  * parsed GUI elements. Details can be found in the manual pages of the respective parsing commands.
  * </p>
@@ -68,16 +69,8 @@
     /**
      * <p>
-     * the pattern used for parsing parsing parameters
-     * </p>
-     */
-    private Pattern htmlElementSpecPattern =
-        Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-#]+)\\))?");
-    
-    /**
-     * <p>
-     * parameters to influence parsing
-     * </p>
-     */
-    private Map<String, List<String>> parseParams;
+     * file containing parameters to influence parsing
+     * </p>
+     */
+    private String parseParamFile;
 
     /**
@@ -86,23 +79,15 @@
      * </p>
      */
-    private Map<String, String> idReplacements;
-
-    /**
-     * <p>
-     * initializes the parser with the parsing parameters to be considered
+    private Map<String, List<ReplacementSpecification>> replacementSpecifications;
+
+    /**
+     * <p>
+     * initializes the parser with the file containing parsing parameters to be considered
      * </p>
      *
-     * @param parseParams the parsing parameters to be considered
-     */
-    public HTMLLogParser(Map<String, List<String>> parseParams) {
-        this.parseParams = parseParams;
-        
-        for (String paramKey : parseParams.keySet()) {
-            if (!"clearId".equals(paramKey) && !"clearIndex".equals(paramKey) &&
-                !"idReplacements".equals(paramKey))
-            {
-                throw new IllegalArgumentException("unknown parse parameter key " + paramKey);
-            }
-        }
+     * @param parseParamFile the parsing parameters to be considered
+     */
+    public HTMLLogParser(String parseParamFile) {
+        this.parseParamFile = parseParamFile;
     }
 
@@ -114,4 +99,6 @@
         throws SAXException
     {
+        ensureParsingParameters();
+        
         HTMLGUIElementSpec specification = null;
         
@@ -182,14 +169,22 @@
                 String htmlId = parameters.get("htmlid");
                 
-                if (clearIndex(tagName, index, htmlId, parent)) {
-                    index = -1;
-                }
-                
-                String idReplacement = replaceHTMLId(tagName, index, htmlId, parent);
-                if (idReplacement != null) {
-                    htmlId = idReplacement;
-                }
-                else if (clearHTMLId(tagName, index, htmlId, parent)) {
-                    htmlId = null;
+                String replacement = getReplacementMapping(tagName, index, htmlId, parent);
+                
+                if (replacement != null) {
+                    if (replacement.startsWith("CLEAR_INDEX,")) {
+                        index = -1;
+                        replacement = replacement.substring("CLEAR_INDEX,".length());
+                    }
+                    else if ("CLEAR_INDEX".equals(replacement)) {
+                        index = -1;
+                        replacement = htmlId;
+                    }
+                    
+                    if ("".equals(replacement)) {
+                        htmlId = null;
+                    }
+                    else {
+                        htmlId = replacement;
+                    }
                 }
                 
@@ -226,52 +221,113 @@
         }
     }
-
-    /**
-     * <p>
-     * checks if for a specific GUI element the index shall be ignored or not by considering the
-     * parsing parameters.
+    
+    /**
+     * <p>
+     * returns the replacement mapping for the tag specified by the parameters, if a mapping exists.
      * </p>
      *
-     * @param tagName the tag of the considered GUI element
-     * @param index   the index of the GUI element
-     * @param id      the id of the GUI element
-     * @param parent  the parent GUI element of the considered GUI element
+     * @param tagName      the tag of the considered GUI element
+     * @param index        the index of the GUI element
+     * @param id           the id of the GUI element
+     * @param parent       the parent GUI element of the considered GUI element
      * 
-     * @return true if the index shall be ignored, false else.
-     */
-    private boolean clearIndex(String tagName, int index, String id, HTMLGUIElement parent) {
-        return clearSomething("clearIndex", tagName, index, id, parent);
-    }
-
-    /**
-     * <p>
-     * checks if the parsing parameters define a replacement for the id of the given GUI element
-     * and if so returns this replacement
-     * </p>
-     *
-     * @param tagName the tag of the considered GUI element
-     * @param index   the index of the GUI element
-     * @param id      the id of the GUI element
-     * @param parent  the parent GUI element of the considered GUI element
-     * 
-     * @return the identified replacement
-     */
-    private String replaceHTMLId(String tagName, int index, String htmlId, HTMLGUIElement parent)
-        throws SAXException
+     * @return the replacement mapping, if any is configured; null else
+     */
+    private String getReplacementMapping(String         tagName,
+                                         int            index,
+                                         String         htmlId,
+                                         HTMLGUIElement parent)
     {
-        if ((idReplacements == null) && (parseParams.containsKey("idReplacements"))) {
-            idReplacements = new HashMap<String, String>();
-            for (String fileName : parseParams.get("idReplacements")) {
+        List<ReplacementSpecification> mappingCandidates = replacementSpecifications.get(tagName);
+        
+        if (mappingCandidates != null) {
+            for (ReplacementSpecification replacementSpec : mappingCandidates) {
+                if (replacementSpec.matches(tagName, index, htmlId, parent)) {
+                    return replacementSpec.getReplacement();
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String, Map)
+     */
+    @Override
+    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
+        String targetId = parameters.get("target");
+        
+        if (targetId == null) {
+            if (replacementSpecifications.size() != 0) {
+                throw new SAXException
+                    ("old log file versions can not be parsed with parse parameters");
+            }
+            
+            String targetDocument = parameters.get("targetDocument");
+            String targetDOMPath = parameters.get("targetDOMPath");
+            
+            if ((targetDocument == null) || (targetDOMPath == null)) {
+                throw new SAXException("event has no target defined");
+            }
+            
+            targetId = determineTargetId(targetDocument, targetDOMPath);
+            
+            if (targetId == null) {
+                // the target id can not be determined yet
+                return false;
+            }
+        }
+        
+        IGUIElement target = super.getGUIElementTree().find(targetId);
+        
+        if (target == null) {
+            // event not processible yet
+            return false;
+        }
+
+        IEventType eventType =
+            HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target);
+        
+        if (eventType != null) {
+            Event event = new Event(eventType, target);
+
+            String timestampStr = parameters.get("timestamp");
+        
+            if (timestampStr != null) {
+                event.setTimestamp(Long.parseLong(timestampStr));
+            }
+
+            ((HTMLGUIElement) event.getTarget()).markUsed();
+        
+            super.addToSequence(event);
+        }
+        // else ignore unknown event type
+
+        return true;
+    }
+
+    /**
+     * <p>
+     * reads parsing parameters from the config file and makes them available for the parsing
+     * process
+     * </p>
+     */
+    private void ensureParsingParameters() throws SAXException {
+        if (replacementSpecifications == null) {
+            replacementSpecifications = new HashMap<String, List<ReplacementSpecification>>();
+            
+            if (parseParamFile != null) {
                 Properties props = new Properties();
                 FileInputStream stream = null;
                 try {
-                    stream = new FileInputStream(new File(fileName));
+                    stream = new FileInputStream(new File(parseParamFile));
                     props.load(stream);
                 }
                 catch (FileNotFoundException e) {
-                    throw new SAXException("could not find file " + fileName, e);
+                    throw new SAXException("could not find file " + parseParamFile, e);
                 }
                 catch (IOException e) {
-                    throw new SAXException("error reading file " + fileName, e);
+                    throw new SAXException("error reading file " + parseParamFile, e);
                 }
                 finally {
@@ -285,245 +341,23 @@
                     }
                 }
-                
+
                 for (Map.Entry<Object, Object> entry : props.entrySet()) {
-                    idReplacements.put((String) entry.getKey(), (String) entry.getValue());
-                }
-            }
-        }
-        
-        if (idReplacements != null) {
-            for (Map.Entry<String, String> replacementSpec : idReplacements.entrySet()) {
-                String tagSpec = replacementSpec.getKey();
-
-                if (tagSpec.startsWith("/")) {
-                    throw new IllegalArgumentException("can not handle absolute specifications");
-                }
-                else if (tagSpec.endsWith("/")) {
-                    throw new IllegalArgumentException("specifications may not end with a /");
-                }
-
-                String[] tagSpecs = tagSpec.split("/");
-
-                if (tagMatchesTagSpec(tagName, index, htmlId, parent, tagSpecs)) {
-                    return replacementSpec.getValue();
-                }
-            }
-        }
-        
-        return null;
-    }
-
-    /**
-     * <p>
-     * checks if for a specific GUI element the id shall be ignored or not by considering the
-     * parsing parameters.
-     * </p>
-     *
-     * @param tagName the tag of the considered GUI element
-     * @param index   the index of the GUI element
-     * @param id      the id of the GUI element
-     * @param parent  the parent GUI element of the considered GUI element
-     * 
-     * @return true if the id shall be ignored, false else.
-     */
-    private boolean clearHTMLId(String tagName, int index, String id, HTMLGUIElement parent) {
-        return clearSomething("clearId", tagName, index, id, parent);
+                    ReplacementSpecification replSpec = new ReplacementSpecification
+                        ((String) entry.getKey(), (String) entry.getValue());
+                    
+                    List<ReplacementSpecification> similarReplSpecs =
+                        replacementSpecifications.get(replSpec.getLastTagName());
+                    
+                    if (similarReplSpecs == null) {
+                        similarReplSpecs = new LinkedList<ReplacementSpecification>();
+                        replacementSpecifications.put(replSpec.getLastTagName(), similarReplSpecs);
+                    }
+                    
+                    similarReplSpecs.add(replSpec);
+                }
+            }
+        }
     }
     
-    /**
-     * <p>
-     * convenience method to check for the existence for specific parsing parameters for clearing
-     * ids or indexes of GUI elements.
-     * </p>
-     *
-     * @param parseParamId the id of the parsing parameter to be checked for the GUI element
-     * @param tagName      the tag of the considered GUI element
-     * @param index        the index of the GUI element
-     * @param id           the id of the GUI element
-     * @param parent       the parent GUI element of the considered GUI element
-     * 
-     * @return true if the denoted parse parameter is set to ignore, false else.
-     */
-    private boolean clearSomething(String         parseParamId,
-                                   String         tagName,
-                                   int            index,
-                                   String         id,
-                                   HTMLGUIElement parent)
-    {
-        if (parseParams.containsKey(parseParamId)) {
-            for (String spec : parseParams.get(parseParamId)) {
-                // determine the specification parts
-                if (spec.startsWith("/")) {
-                    throw new IllegalArgumentException("can not handle absolute specifications");
-                }
-                else if (spec.endsWith("/")) {
-                    throw new IllegalArgumentException("specifications may not end with a /");
-                }
-                
-                String[] tagSpecs = spec.split("/");
-                
-                if (tagMatchesTagSpec(tagName, index, id, parent, tagSpecs)) {
-                    return true;
-                }
-            }
-        }
-        
-        return false;
-    }
-
-    /**
-     * <p>
-     * convenience method to check if a given GUI element matches a specification tags provided
-     * through the parsing parameters.
-     * </p>
-     *
-     * @param tagName  the tag of the considered GUI element
-     * @param index    the index of the GUI element
-     * @param id       the id of the GUI element
-     * @param parent   the parent GUI element of the considered GUI element
-     * @param tagSpecs the specification of a GUI element to match against the given GUI element
-     * 
-     * @return true if the denoted parse parameter is set to ignore, false else.
-     */
-    private boolean tagMatchesTagSpec(String         tagName,
-                                      int            index,
-                                      String         id,
-                                      HTMLGUIElement parent,
-                                      String[]       tagSpecs)
-    {
-        
-        if (tagSpecs.length > 0) {
-            Matcher matcher = htmlElementSpecPattern.matcher(tagSpecs[tagSpecs.length - 1]);
-            
-            if (!matcher.matches()) {
-                throw new IllegalArgumentException
-                    ("illegal tag specification " + tagSpecs[tagSpecs.length - 1]);
-            }
-            
-            if (!tagName.equals(matcher.group(1))) {
-                return false;
-            }
-            
-            String idCondition = matcher.group(4);
-            
-            if (idCondition != null) {
-                if (!idCondition.equals(id)) {
-                    // check if the id condition would match with ignoring specific characters
-                    if ((id != null) && (idCondition.indexOf('#') > -1)) {
-                        // first of all, the length must match
-                        if (idCondition.length() != id.length()) {
-                            return false;
-                        }
-                        
-                        for (int i = 0; i < idCondition.length(); i++) {
-                            if ((idCondition.charAt(i) != '#') &&
-                                (idCondition.charAt(i) != id.charAt(i)))
-                            {
-                                // if there is a character that is neither ignored not matches
-                                // the condition at a specific position, return "no match"
-                                return false;
-                            }
-                        }
-                        
-                    }
-                    else {
-                        // no condition ignoring specific characters
-                        return false;
-                    }
-                }
-            }
-            
-            String indexCondition = matcher.group(3);
-            
-            if (indexCondition != null) {
-                try {
-                    if (index != Integer.parseInt(indexCondition)) {
-                        return false;
-                    }
-                }
-                catch (NumberFormatException e) {
-                    throw new IllegalArgumentException
-                        ("illegal tag index specification " + indexCondition, e);
-                }
-            }
-            
-            if (tagSpecs.length > 1) {
-                if (parent instanceof HTMLPageElement) {
-                    return tagMatchesTagSpec(((HTMLPageElement) parent).getTagName(),
-                                             ((HTMLPageElement) parent).getIndex(),
-                                             ((HTMLPageElement) parent).getHtmlId(),
-                                             (HTMLGUIElement) parent.getParent(),
-                                             Arrays.copyOfRange(tagSpecs, 0, tagSpecs.length - 1));
-                }
-                else {
-                    throw new IllegalArgumentException
-                        ("specification matches documents or servers. This is not supported yet.");
-                }
-            }
-            else {
-                return true;
-            }
-        }
-        else {
-            return true;
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String, Map)
-     */
-    @Override
-    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
-        String targetId = parameters.get("target");
-        
-        if (targetId == null) {
-            if (parseParams.size() != 0) {
-                throw new SAXException
-                    ("old log file versions can not be parsed with parse parameters");
-            }
-            
-            String targetDocument = parameters.get("targetDocument");
-            String targetDOMPath = parameters.get("targetDOMPath");
-            
-            if ((targetDocument == null) || (targetDOMPath == null)) {
-                throw new SAXException("event has no target defined");
-            }
-            
-            targetId = determineTargetId(targetDocument, targetDOMPath);
-            
-            if (targetId == null) {
-                // the target id can not be determined yet
-                return false;
-            }
-        }
-        
-        IGUIElement target = super.getGUIElementTree().find(targetId);
-        
-        if (target == null) {
-            // event not processible yet
-            return false;
-        }
-
-        IEventType eventType =
-            HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target);
-        
-        if (eventType != null) {
-            Event event = new Event(eventType, target);
-
-            String timestampStr = parameters.get("timestamp");
-        
-            if (timestampStr != null) {
-                event.setTimestamp(Long.parseLong(timestampStr));
-            }
-
-            ((HTMLGUIElement) event.getTarget()).markUsed();
-        
-            super.addToSequence(event);
-        }
-        // else ignore unknown event type
-
-        return true;
-    }
-
     /**
      * <p>
@@ -647,3 +481,376 @@
     }
 
+    /**
+     * <p>specification for a replacement consisting of path of tag or document specifications
+     * and the appropriate replacement.</p>
+     */
+    private static class ReplacementSpecification {
+        
+        /**
+         * <p>
+         * the pattern used for parsing parsing parameters
+         * </p>
+         */
+        private Pattern htmlElementSpecPattern = Pattern.compile
+            ("(document\\(path=([\\w/-]+)\\))|((\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-_#]+)\\))?)");
+        
+        /**
+         * <p>
+         * the path of specifications (tags and document) specifying the tag for which this
+         * replacement is specified
+         * </p>
+         */
+        private List<Spec> specs = new LinkedList<Spec>();
+        
+        /**
+         * <p>
+         * the name of the last tag in the specification path (used for indexing purposes)
+         * </p>
+         */
+        private String lastTagName;
+        
+        /**
+         * <p>
+         * the configured replacement
+         * </p>
+         */
+        private String replacement;
+
+        /**
+         * <p>
+         * initializes the specification with the key/value strings from the config file. Parses
+         * the key to get the specification path consisting of, optionally, a document
+         * specification and one or more tag specification.
+         * </p>
+         */
+        public ReplacementSpecification(String tagSpec, String replacement) {
+            List<String> tagSpecs = split(tagSpec);
+            
+            for (int i = 0; i < tagSpecs.size(); i++) {
+                Matcher matcher = htmlElementSpecPattern.matcher(tagSpecs.get(i));
+                
+                if (!matcher.matches()) {
+                    throw new IllegalArgumentException
+                        ("illegal tag specification " + tagSpecs.get(i));
+                }
+                
+                if (matcher.group(1) != null) {
+                    this.specs.add(new DocumentSpec(matcher.group(2)));
+                }
+                else if (matcher.group(4) != null) {
+                    String indexConditionStr = matcher.group(6);
+                    Integer indexCondition = null;
+                
+                    if (indexConditionStr != null) {
+                        try {
+                            indexCondition = Integer.parseInt(indexConditionStr);
+                        }
+                        catch (NumberFormatException e) {
+                            throw new IllegalArgumentException
+                                ("illegal tag index specification " + indexConditionStr, e);
+                        }
+                    }
+                
+                    this.specs.add
+                        (new TagSpec(matcher.group(4), indexCondition, matcher.group(7)));
+                }
+            }
+            
+            this.lastTagName = ((TagSpec) this.specs.get(this.specs.size() - 1)).getTagName();
+            
+            this.replacement = replacement;
+        }
+
+        /**
+         * <p>
+         * convenience method to split the key of a key/value pair from the config file into its
+         * parts
+         * </p>
+         */
+        private List<String> split(String tagSpec) {
+            List<String> specs = new LinkedList<String>();
+            
+            StringBuffer currentSpec = new StringBuffer();
+            int openBraces = 0;
+            
+            for (int i = 0; i < tagSpec.length(); i++) {
+                char curChar = tagSpec.charAt(i);
+                if ((openBraces == 0) && ('/' == curChar) && (currentSpec.length() > 0)) {
+                    specs.add(currentSpec.toString());
+                    currentSpec.setLength(0);
+                }
+                else {
+                    if ('(' == curChar) {
+                        openBraces++;
+                    }
+                    else if (')' == curChar) {
+                        openBraces--;
+                    }
+                    currentSpec.append(curChar);
+                }
+            }
+            
+            if (currentSpec.length() > 0) {
+                specs.add(currentSpec.toString());
+            }
+            
+            return specs;
+        }
+
+        /**
+         * <p>
+         * checks, if the tag identified by the parameters matches this specificaiton.
+         * </p>
+         */
+        private boolean matches(String tagName, int index, String htmlId, HTMLGUIElement parent) {
+            String currentTagName = tagName;
+            int currentIndex = index;
+            String currentHtmlId = htmlId;
+            String currentPath = null;
+            HTMLGUIElement currentParent = parent;
+            
+            int i = specs.size() - 1;
+            
+            while (i >= 0) {
+                if ((specs.get(i) instanceof TagSpec) &&
+                    (!((TagSpec) specs.get(i)).matches(currentTagName, currentIndex, currentHtmlId)))
+                {
+                    return false;
+                }
+                else if ((specs.get(i) instanceof DocumentSpec) &&
+                         (!((DocumentSpec) specs.get(i)).matches(currentPath)))
+                {
+                    return false;
+                }
+                
+                i--;
+                
+                if (i >= 0) {
+                    if (currentParent instanceof HTMLPageElement) {
+                        currentTagName = ((HTMLPageElement) currentParent).getTagName();
+                        currentIndex = ((HTMLPageElement) currentParent).getIndex();
+                        currentHtmlId = ((HTMLPageElement) currentParent).getHtmlId();
+                        currentPath = null;
+                        currentParent = (HTMLGUIElement) currentParent.getParent();
+                     }
+                    else if (currentParent instanceof HTMLDocument) {
+                        currentTagName = null;
+                        currentIndex = Integer.MIN_VALUE;
+                        currentHtmlId = null;
+                        currentPath = ((HTMLDocument) currentParent).getPath();
+                        currentParent = (HTMLGUIElement) currentParent.getParent();
+                    }
+                    else {
+                        throw new IllegalArgumentException
+                            ("specification matches documents or servers. This is not supported yet.");
+                    }
+                }
+            }
+            
+            return true;
+        }
+
+        /**
+         * <p>
+         * returns the specified replacement
+         * </p>
+         */
+        private String getReplacement() {
+            return replacement;
+        }
+
+        /**
+         * <p>
+         * returns the name of the last tag specified in the specification path
+         * </p>
+         */
+        private String getLastTagName() {
+            return lastTagName;
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            StringBuffer result = new StringBuffer();
+            for (Spec spec : specs) {
+                if (result.length() > 0) {
+                    result.append("/");
+                }
+                result.append(spec);
+            }
+            
+            result.append('=');
+            result.append(replacement);
+            
+            return result.toString();
+        }
+        
+    }
+
+    /**
+     * <p>
+     * parent type for document and tag specifications
+     * </p>
+     */
+    private static interface Spec { }
+
+    /**
+     * <p>
+     * specification of a document
+     * </p>
+     */
+    private static class DocumentSpec implements Spec {
+        
+        /**
+         * <p>
+         * the part of the path the document path must have to match this specification
+         * </p>
+         */
+        private String pathPart;
+
+        /**
+         * <p>
+         * initializes the document specification with the path part
+         * </p>
+         */
+        private DocumentSpec(String pathPart) {
+            this.pathPart = pathPart;
+        }
+
+        /**
+         * <p>
+         * returns true if the provided path contains the path part provided to the parameter
+         * </p>
+         */
+        private boolean matches(String path) {
+            return path.contains(pathPart);
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return "document(path=" + pathPart + ")";
+        }
+    }
+
+    /**
+     * <p>
+     * specification for a tag containing a tag name and either an index or id condition.
+     * </p>
+     */
+    private static class TagSpec implements Spec {
+
+        /**
+         * <p>
+         * the name of the tag to match
+         * </p>
+         */
+        private String tagName;
+        
+        /**
+         * <p>
+         * the index of the tag to match
+         * </p>
+         */
+        private Integer indexCondition;
+        
+        /**
+         * <p>
+         * the id of the tag to match
+         * </p>
+         */
+        private String idCondition;
+
+        /**
+         * <p>
+         * initializes the specification with all required parameters
+         * </p>
+         */
+        private TagSpec(String tagName, Integer indexCondition, String idCondition) {
+            this.tagName = tagName;
+            this.indexCondition = indexCondition;
+            this.idCondition = idCondition;
+        }
+
+        /**
+         * <p>
+         * returns true if the provided tag information matches this specification. The id is
+         * checked first. If the id condition has a # at some position, the respective element
+         * of the provided id is ignored.
+         * </p>
+         */
+        private boolean matches(String tagName, int index, String htmlId) {
+            if (!this.tagName.equals(tagName)) {
+                return false;
+            }
+            
+            if (idCondition != null) {
+                if (!idCondition.equals(htmlId)) {
+                    // check if the id condition would match with ignoring specific characters
+                    if ((htmlId != null) && (idCondition.indexOf('#') > -1)) {
+                        // first of all, the length must match
+                        if (idCondition.length() != htmlId.length()) {
+                            return false;
+                        }
+                        
+                        for (int i = 0; i < idCondition.length(); i++) {
+                            if ((idCondition.charAt(i) != '#') &&
+                                (idCondition.charAt(i) != htmlId.charAt(i)))
+                            {
+                                // if there is a character that is neither ignored nor matches
+                                // the condition at a specific position, return "no match"
+                                return false;
+                            }
+                        }
+                        
+                    }
+                    else {
+                        // no condition ignoring specific characters
+                        return false;
+                    }
+                }
+            }
+            
+            if ((indexCondition != null) && (index != indexCondition)) {
+                return false;
+            }
+            
+            return true;
+        }
+
+        /**
+         * <p>
+         * returns the name of the tags matched by this specification
+         * </p>
+         */
+        private String getTagName() {
+            return tagName;
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            StringBuffer result = new StringBuffer(tagName);
+            
+            if (idCondition != null) {
+                result.append("(htmlId=");
+                result.append(idCondition);
+                result.append(')');
+            }
+            else if (indexCondition != null) {
+                result.append('[');
+                result.append(indexCondition);
+                result.append(']');
+            }
+            
+            return result.toString();
+        }
+        
+    }
 }
