public ParseResult validate(String input) {

		log.debug("validating");
		ParseResult result = new ParseResult();
		result.setValid(true);

		Stack<String> validationStack = new Stack<String>();
		Pattern p = Pattern.compile("(<!--([^/]\\w*?)-->|<!--/(\\w+?)-->)", Pattern.DOTALL);
		Matcher m = p.matcher(input);
		boolean firstRun = true;

		while (m.find()) {

			if (m.group(2) != null) {
				// start tag
				String openingTag = m.group(2);
				log.trace("Starttag: " + openingTag);

				if (!firstRun) {
					if (validationStack.isEmpty()) {
						result.setValid(false);
						result.setErrorMessage("Second root element found: " + openingTag);
						result.setErrorLine(StringUtil.countOccurrences(input.substring(0, m.end() - 1), '\n') + 1);
						break;
					}
				}
				firstRun = false;

				validationStack.push(openingTag);
			} else if (m.group(3) != null) {
				// end tag
				String closingTag = m.group(3);
				log.trace("Endtag: " + closingTag);

				String top = null;
				try {
					top = validationStack.pop();
				} catch (EmptyStackException e) {
					result.setValid(false);
					result.setErrorMessage("No opening tag for " + closingTag);
					result.setErrorLine(StringUtil.countOccurrences(input.substring(0, m.end() - 1), '\n') + 1);
					break;
				}

				if (!closingTag.equals(top)) {
					result.setValid(false);
					result.setErrorMessage("No closing tag for " + top);
					result.setErrorLine(StringUtil.countOccurrences(input.substring(0, m.end() - 1), '\n') + 1);
					break;
				}
			} else {
				// should not happen
				log.error("parsing error");
				result.setValid(false);
				result.setErrorMessage("Parsing error");
				break;
			}
		}

		if (result.isValid() && !validationStack.isEmpty()) {
			String top = validationStack.pop();
			result.setValid(false);
			result.setErrorMessage("No closing tag for " + top);
			result.setErrorLine(0); // origin of wrong tag is unknown
		}

		log.debug("validation complete, result: " + result);

		return result;
	}