Tag Validator
HardUpdated: Oct 13, 2025
Practice on:
Problem
Given a string representing a code snippet, implement a tag validator to parse the code and return whether it is valid.
A code snippet is valid if all the following rules hold:
- The code must be wrapped in a valid closed tag. Otherwise, the code is invalid.
- A closed tag (not necessarily valid) has exactly the following format :
<TAG_NAME>TAG_CONTENT</TAG_NAME>. Among them,<TAG_NAME>is the start tag, and</TAG_NAME>is the end tag. The TAG_NAME in start and end tags should be the same. A closed tag is valid if and only if the TAG_NAME and TAG_CONTENT are valid. - A valid
TAG_NAMEonly contain upper-case letters , and has length in range [1,9]. Otherwise, theTAG_NAMEis invalid. - A valid
TAG_CONTENTmay contain other valid closed tags , cdata and any characters (see note1) EXCEPT unmatched<, unmatched start and end tag, and unmatched or closed tags with invalid TAG_NAME. Otherwise, theTAG_CONTENTis invalid. - A start tag is unmatched if no end tag exists with the same TAG_NAME, and vice versa. However, you also need to consider the issue of unbalanced when tags are nested.
- A
<is unmatched if you cannot find a subsequent>. And when you find a<or</, all the subsequent characters until the next>should be parsed as TAG_NAME (not necessarily valid). - The cdata has the following format :
<![CDATA[CDATA_CONTENT]]>. The range ofCDATA_CONTENTis defined as the characters between<![CDATA[and the first subsequent]]>. CDATA_CONTENTmay contain any characters. The function of cdata is to forbid the validator to parseCDATA_CONTENT, so even it has some characters that can be parsed as tag (no matter valid or invalid), you should treat it as regular characters.
Examples
Example 1
Input: code = "<DIV>This is the first line <![CDATA[<div>]]></DIV>"
Output: true
Explanation:
The code is wrapped in a closed tag : <DIV> and </DIV>.
The TAG_NAME is valid, the TAG_CONTENT consists of some characters and cdata.
Although CDATA_CONTENT has an unmatched start tag with invalid TAG_NAME, it should be considered as plain text, not parsed as a tag.
So TAG_CONTENT is valid, and then the code is valid. Thus return true.
Example 2
Input: code = "<DIV>>> ![cdata[]] <![CDATA[<div>]>]]>]]>>]</DIV>"
Output: true
Explanation:
We first separate the code into : start_tag|tag_content|end_tag.
start_tag -> **" <DIV>"**
end_tag -> **" </DIV>"**
tag_content could also be separated into : text1|cdata|text2.
text1 -> **" >> ![cdata[]] "**
cdata -> **" <![CDATA[<div>]>]]>"**, where the CDATA_CONTENT is **" <div>]>"**
text2 -> **"]]>>]"**
The reason why start_tag is NOT **" <DIV>>>"** is because of the rule 6.
The reason why cdata is NOT **" <![CDATA[<div>]>]]>]]>"** is because of the rule 7.
Example 3
Input: code = "<A> <B> </A> </B>"
Output: false
Explanation: Unbalanced. If "<A>" is closed, then "<B>" must be unmatched, and vice versa.
Constraints
1 <= code.length <= 500codeconsists of English letters, digits,'<','>','/','!','[',']','.', and' '.
Solution
Method 1 - Stack-based Tag Parsing
Intuition
We use a stack to track open tags. For each tag, we check validity and match with the stack. CDATA sections are skipped as plain text. The code is valid if the stack is empty at the end and all rules are satisfied.
Approach
We use a stack to track open tags and parse the string character by character. For each tag, we check validity and match with the stack. CDATA sections are skipped as plain text. The code is valid if the stack is empty at the end and all rules are satisfied.
Complexity
- ⏰ Time complexity:
O(n)– We scan the input string once and process each character in O(1) amortized time. - 🧺 Space complexity:
O(n)– The stack can grow up to the number of nested tags in the worst case.
Code
C++
#include <stack>
#include <string>
using namespace std;
class Solution {
public:
bool isValid(string code) {
stack<string> st;
int i = 0, n = code.size();
while (i < n) {
if (i > 0 && st.empty()) return false;
if (i + 9 <= n && code.substr(i, 9) == "<![CDATA[") {
int j = code.find("]]>", i);
if (j == string::npos) return false;
i = j + 3;
} else if (i + 2 <= n && code.substr(i, 2) == "</") {
int j = code.find('>', i);
if (j == string::npos) return false;
string tag = code.substr(i + 2, j - (i + 2));
if (st.empty() || !isValidTag(tag) || st.top() != tag) return false;
st.pop();
i = j + 1;
} else if (code[i] == '<') {
int j = code.find('>', i);
if (j == string::npos) return false;
string tag = code.substr(i + 1, j - (i + 1));
if (!isValidTag(tag)) return false;
st.push(tag);
i = j + 1;
} else {
i++;
}
}
return st.empty();
}
private:
bool isValidTag(const string& tag) {
if (tag.size() < 1 || tag.size() > 9) return false;
for (char c : tag) if (c < 'A' || c > 'Z') return false;
return true;
}
};
Go
package solution
func isValid(code string) bool {
n := len(code)
i := 0
stack := make([]string, 0)
for i < n {
if i > 0 && len(stack) == 0 { return false }
if i+9 <= n && code[i:i+9] == "<![CDATA[" {
j := i + 9
found := -1
for j+2 < n {
if code[j] == ']' && code[j+1] == ']' && code[j+2] == '>' { found = j; break }
j++
}
if found == -1 { return false }
i = found + 3
} else if i+2 <= n && i+1 < n && code[i:i+2] == "</" {
j := i + 2
for j < n && code[j] != '>' { j++ }
if j >= n { return false }
tag := code[i+2:j]
if !isValidTag(tag) { return false }
if len(stack) == 0 || stack[len(stack)-1] != tag { return false }
stack = stack[:len(stack)-1]
i = j + 1
} else if code[i] == '<' {
j := i + 1
for j < n && code[j] != '>' { j++ }
if j >= n { return false }
tag := code[i+1:j]
if !isValidTag(tag) { return false }
stack = append(stack, tag)
i = j + 1
} else {
i++
}
}
return len(stack) == 0
}
func isValidTag(tag string) bool {
if len(tag) < 1 || len(tag) > 9 { return false }
for i := 0; i < len(tag); i++ {
if tag[i] < 'A' || tag[i] > 'Z' { return false }
}
return true
}
Java
import java.util.*;
class Solution {
public boolean isValid(String code) {
Deque<String> stack = new ArrayDeque<>();
int i = 0, n = code.length();
while (i < n) {
if (i > 0 && stack.isEmpty()) return false;
if (code.startsWith("<![CDATA[", i)) {
int j = code.indexOf("]]>", i);
if (j < 0) return false;
i = j + 3;
} else if (code.startsWith("</", i)) {
int j = code.indexOf('>', i);
if (j < 0) return false;
String tag = code.substring(i + 2, j);
if (stack.isEmpty() || isInvalidTag(tag) || !tag.equals(stack.peek())) return false;
stack.pop();
i = j + 1;
} else if (code.startsWith("<", i)) {
int j = code.indexOf('>', i);
if (j < 0) return false;
String tag = code.substring(i + 1, j);
if (isInvalidTag(tag)) return false;
stack.push(tag);
i = j + 1;
} else {
i++;
}
}
return stack.isEmpty();
}
private boolean isInvalidTag(String tag) {
if (tag.isEmpty() || tag.length() > 9) return true;
for (char c : tag.toCharArray()) if (c < 'A' || c > 'Z') return true;
return false;
}
}
Kotlin
class Solution {
fun isValid(code: String): Boolean {
val n = code.length
var i = 0
val stack = ArrayDeque<String>()
while (i < n) {
if (i > 0 && stack.isEmpty()) return false
if (i + 9 <= n && code.substring(i, i+9) == "<![CDATA[") {
var j = i + 9
var found = -1
while (j + 2 < n) {
if (code[j] == ']' && code[j+1] == ']' && code[j+2] == '>') { found = j; break }
j++
}
if (found == -1) return false
i = found + 3
} else if (i + 2 <= n && code.substring(i, i+2) == "</") {
var j = i + 2
while (j < n && code[j] != '>') j++
if (j >= n) return false
val tag = code.substring(i+2, j)
if (!isValidTag(tag) || stack.isEmpty() || stack.peek() != tag) return false
stack.pop()
i = j + 1
} else if (code[i] == '<') {
var j = i + 1
while (j < n && code[j] != '>') j++
if (j >= n) return false
val tag = code.substring(i+1, j)
if (!isValidTag(tag)) return false
stack.push(tag)
i = j + 1
} else {
i++
}
}
return stack.isEmpty()
}
private fun isValidTag(tag: String): Boolean {
if (tag.length < 1 || tag.length > 9) return false
for (c in tag) if (c < 'A' || c > 'Z') return false
return true
}
}
Python
class Solution:
def isValid(self, code: str) -> bool:
stack = []
i, n = 0, len(code)
while i < n:
if i > 0 and not stack:
return False
if code.startswith('<![CDATA[', i):
j = code.find(']]>', i)
if j == -1:
return False
i = j + 3
elif code.startswith('</', i):
j = code.find('>', i)
if j == -1:
return False
tag = code[i+2:j]
if not stack or not (1 <= len(tag) <= 9 and tag.isupper()) or stack[-1] != tag:
return False
stack.pop()
i = j + 1
elif code.startswith('<', i):
j = code.find('>', i)
if j == -1:
return False
tag = code[i+1:j]
if not (1 <= len(tag) <= 9 and tag.isupper()):
return False
stack.append(tag)
i = j + 1
else:
i += 1
return not stack
Rust
impl Solution {
pub fn isValid(code: String) -> bool {
let mut stack: Vec<Vec<u8>> = Vec::new();
let bytes = code.as_bytes();
let mut i = 0usize;
while i < bytes.len() {
if i > 0 && stack.is_empty() { return false; }
if i + 9 <= bytes.len() && &bytes[i..i+9] == b"<![CDATA[" {
if let Some(pos) = bytes[i..].windows(3).position(|w| w == b"]]>") {
i += pos + 3;
} else { return false; }
} else if i + 2 <= bytes.len() && &bytes[i..i+2] == b"</" {
if let Some(pos) = bytes[i..].iter().position(|&c| c == b'>') {
let tag = bytes[i+2..i+pos].to_vec();
if stack.is_empty() || tag.len() < 1 || tag.len() > 9 || !tag.iter().all(|&c| c >= b'A' && c <= b'Z') || stack.last().unwrap() != &tag {
return false;
}
stack.pop();
i += pos + 1;
} else { return false; }
} else if bytes[i] == b'<'[0] {
if let Some(pos) = bytes[i..].iter().position(|&c| c == b'>') {
let tag = bytes[i+1..i+pos].to_vec();
if tag.len() < 1 || tag.len() > 9 || !tag.iter().all(|&c| c >= b'A' && c <= b'Z') {
return false;
}
stack.push(tag);
i += pos + 1;
} else { return false; }
} else {
i += 1;
}
}
stack.is_empty()
}
}
TypeScript
function isValid(code: string): boolean {
const stack: string[] = [];
let i = 0;
while (i < code.length) {
if (i > 0 && stack.length === 0) return false;
if (code.startsWith('<![CDATA[', i)) {
const j = code.indexOf(']]>', i);
if (j === -1) return false;
i = j + 3;
} else if (code.startsWith('</', i)) {
const j = code.indexOf('>', i);
if (j === -1) return false;
const tag = code.slice(i + 2, j);
if (stack.length === 0 || !/^[A-Z]{1,9}$/.test(tag) || stack[stack.length - 1] !== tag) return false;
stack.pop();
i = j + 1;
} else if (code[i] === '<') {
const j = code.indexOf('>', i);
if (j === -1) return false;
const tag = code.slice(i + 1, j);
if (!/^[A-Z]{1,9}$/.test(tag)) return false;
stack.push(tag);
i = j + 1;
} else {
i++;
}
}
return stack.length === 0;
}