Specification
$flags: 'FLEXIBLE_WHITESPACE'
Attribute:
$types:
Identifier: '[a-zA-Z][a-zA-Z0-9-]+'
String: '.*?'
$fields:
name: Identifier
value: String
$patterns: '#{name}=\"#{value}\"'
Node:
Comment: '<!--.*?-->'
Text: '(?<=>)${!Node}[^>\s][^>]*(?=<)'
Tag:
$types:
Identifier: '[a-zA-Z][a-zA-Z0-9-]+'
$fields:
name: Identifier
attributes: Attribute[]
children: Node[]
$patterns:
- '<#{name}( #{attributes})*>(\s*#{children}\s*)*</#{=name}>'
- '<#{name}( #{attributes})*/>'Sample input
<div class="container">
<div class="content" id="content-panel" height="200px">
<h1 id="main-title">Hello, World!</h1>
<br/>
</div>
<!-- <div> Commented Node </div> -->
</div>
Result
[
{
"type": "Node::Tag",
"text": "<div class=\"container\">\n <div class=\"content\" id=\"content-panel\" height=\"200px\">\n <h1 id=\"main-title\">Hello, World!</h1>\n <br/>\n </div>\n <!-- <div> Commented Node </div> -->\n</div>",
"start": 0,
"end": 191,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "div",
"start": 1,
"end": 4
},
{
"type": "Attribute[]",
"field": "attributes",
"text": "class=\"container\"",
"start": 5,
"end": 22,
"items": [
{
"type": "Attribute",
"text": "class=\"container\"",
"start": 5,
"end": 22,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "class",
"start": 5,
"end": 10
},
{
"type": "String",
"field": "value",
"text": "container",
"start": 12,
"end": 21
}
]
}
]
},
{
"type": "Node[]",
"field": "children",
"text": "<div class=\"content\" id=\"content-panel\" height=\"200px\">\n <h1 id=\"main-title\">Hello, World!</h1>\n <br/>\n </div>\n <!-- <div> Commented Node </div> -->",
"start": 26,
"end": 184,
"items": [
{
"type": "Node::Tag",
"text": "<div class=\"content\" id=\"content-panel\" height=\"200px\">\n <h1 id=\"main-title\">Hello, World!</h1>\n <br/>\n </div>",
"start": 26,
"end": 144,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "div",
"start": 27,
"end": 30
},
{
"type": "Attribute[]",
"field": "attributes",
"text": "class=\"content\" id=\"content-panel\" height=\"200px\"",
"start": 31,
"end": 80,
"items": [
{
"type": "Attribute",
"text": "class=\"content\"",
"start": 31,
"end": 46,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "class",
"start": 31,
"end": 36
},
{
"type": "String",
"field": "value",
"text": "content",
"start": 38,
"end": 45
}
]
},
{
"type": "Attribute",
"text": "id=\"content-panel\"",
"start": 47,
"end": 65,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "id",
"start": 47,
"end": 49
},
{
"type": "String",
"field": "value",
"text": "content-panel",
"start": 51,
"end": 64
}
]
},
{
"type": "Attribute",
"text": "height=\"200px\"",
"start": 66,
"end": 80,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "height",
"start": 66,
"end": 72
},
{
"type": "String",
"field": "value",
"text": "200px",
"start": 74,
"end": 79
}
]
}
]
},
{
"type": "Node[]",
"field": "children",
"text": "<h1 id=\"main-title\">Hello, World!</h1>\n <br/>",
"start": 86,
"end": 134,
"items": [
{
"type": "Node::Tag",
"text": "<h1 id=\"main-title\">Hello, World!</h1>",
"start": 86,
"end": 124,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "h1",
"start": 87,
"end": 89
},
{
"type": "Attribute[]",
"field": "attributes",
"text": "id=\"main-title\"",
"start": 90,
"end": 105,
"items": [
{
"type": "Attribute",
"text": "id=\"main-title\"",
"start": 90,
"end": 105,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "id",
"start": 90,
"end": 92
},
{
"type": "String",
"field": "value",
"text": "main-title",
"start": 94,
"end": 104
}
]
}
]
},
{
"type": "Node[]",
"field": "children",
"text": "Hello, World!",
"start": 106,
"end": 119,
"items": [
{
"type": "Node::Text",
"text": "Hello, World!",
"start": 106,
"end": 119
}
]
}
]
},
{
"type": "Node::Tag",
"text": "<br/>",
"start": 129,
"end": 134,
"fields": [
{
"type": "Identifier",
"field": "name",
"text": "br",
"start": 130,
"end": 132
}
]
}
]
}
]
},
{
"type": "Node::Comment",
"text": "<!-- <div> Commented Node </div> -->",
"start": 148,
"end": 184
}
]
}
]
}
]Notes
# HTML Fragment
Recognize a small nested HTML-like fragment.
## Notice
- `Attribute` and `Node::Tag` use scoped `$types` for tag and attribute names.
- `Attribute` extracts `name` and `value`.
- `Node` is the parent type for comments, text, and tags.
- `Node::Tag` references `${Node}` to allow nesting.
- Closing tags use `#{=name}` to reference the opening tag field.
Try adding a nested tag or another attribute.