%YAML 1.2 --- name: XML file_extensions: - xml - xsd - xslt - tld - dtml - rng - rss - opml - svg first_line_match: |- (?x: ^(?: <\?xml\s | \s*<([\w-]+):Envelope\s+xmlns:\1\s*=\s*"http://schemas.xmlsoap.org/soap/envelope/"\s*> | \s*(?i:])) ) ) scope: text.xml variables: # The atomic part of a tag or attribute name without namespace separator `:` identifier: '[[:alpha:]_][[:alnum:]_.-]*' # This is the full XML Name production, but should not be used where namespaces # are possible. Those locations should use a qualified_name. name: '[[:alpha:]:_][[:alnum:]:_.-]*' # The qualified tag name allows a namespace prefix (ns:) followed by a local # name while both parts are validated separately. The namespace is optional, # but is matched valid if not followed by a localname in order to not disturb # writing. qualified_tag_name: |- (?x) (?: (?: ({{identifier}}) # 1: valid namespace | ([^?!/<>\s][^:/<>\s]*) # 2: invalid namespace )(:) )? # namespace is optional (?: ({{identifier}})(?=[/<>\s]) # 3: valid localname | ([^?!/<>\s][^/<>\s]*) # 4: invalid localname ) qualified_attribute_name: |- (?x) (?: (?: ({{identifier}}) # 1: valid namespace | ([^:=/<>\s]+) # 2: invalid namespace )(:) )? # namespace is optional (?: ({{identifier}}) # 3: valid localname | ([^=/<>\s]+?) # 4: invalid localname )(?=[=<>\s]|[/?]>) # A doctype definition identifier is always followed by one of the # characters of `dtd_break`. dtd_break: '[''"\[\]()<>\s]' # A valid or invalid doctype declaration identifiere consists of any # character but one of `dtd_break`. invalid_dtd_name: '[^{{dtd_break}}]+' # A qualified doctype declaration identifier consists of a valid # name which is followed by a valid break character. qualified_dtd_name: '{{name}}(?=[{{dtd_break}}])' contexts: main: - include: preprocessor - include: doctype - include: comment - include: cdata - include: tag - include: entity - include: should-be-entity ###[ CDATA ]################################################################## cdata: - match: (' scope: punctuation.definition.tag.end.xml pop: true ###[ COMMENT ]################################################################ comment: - match: '' scope: punctuation.definition.comment.end.xml pop: true - match: '-{2,}' scope: invalid.illegal.double-hyphen-within-comment.xml ###[ DOCTYPE DECLARATION ]#################################################### doctype: - match: ( scope: punctuation.definition.tag.end.xml pop: true - match: '[/\?]?>' scope: invalid.illegal.bad-tag-end.xml pop: true - include: tag-end-missing-pop dtd-subset-name: - match: '{{qualified_dtd_name}}' scope: variable.other.subset.xml pop: true - include: dtd-common-name dtd-subset-brackets: - match: \[ scope: punctuation.section.brackets.begin.xml set: - meta_scope: meta.brackets.xml meta.internal-subset.xml - match: \] scope: punctuation.section.brackets.end.xml pop: true - include: dtd - include: dtd-else-pop ###[ DTD UNKNOWN ]############################################################ dtd-unknown: - match: (\s]*) captures: 1: punctuation.definition.tag.begin.xml 2: invalid.illegal.bad-tag-name.xml push: - meta_scope: meta.tag.sgml.unknown.xml - include: dtd-end ###[ DTD PROTOTYPES ]######################################################### dtd-common-name: - match: (%){{name}}(;) scope: variable.parameter.xml captures: 1: punctuation.definition.parameter.xml 2: punctuation.terminator.parameter.xml pop: true - match: '{{invalid_dtd_name}}' scope: invalid.illegal.bad-identifier.xml pop: true - include: dtd-else-pop dtd-content-unquoted: - include: string-unquoted - include: dtd-else-pop dtd-content-quoted: - include: string-quoted - include: dtd-else-pop dtd-content-type: - match: (?:PUBLIC|SYSTEM)\b scope: storage.type.external-content.xml pop: true - include: dtd-else-pop dtd-else-pop: # try to keep one whitespace if the end of a subset is detected # in order to scope it as `invalid.illegal.missing-tag-end` - match: (?=\s?\]) pop: true - include: tag-else-pop dtd-end: - match: '>' scope: punctuation.definition.tag.end.xml pop: true - match: \s?(?=[<\]]) scope: invalid.illegal.missing-tag-end.xml pop: true - match: '[/\?]>' scope: invalid.illegal.bad-tag-end.xml pop: true - match: \S scope: invalid.illegal.unexpected.xml entity-parameter: - match: (#)P?CDATA scope: constant.other.placeholder.xml captures: 1: punctuation.definition.constant.xml - match: (%){{name}}(;) scope: variable.parameter.xml captures: 1: punctuation.definition.parameter.xml 2: punctuation.terminator.parameter.xml ###[ XML PREPROCESSOR ]####################################################### preprocessor: # Prolog tags like without respect of details # Examples: # # # # - match: |- (?x) (<\?) # opening \s]) | # invalid mixed or uppercase tag name ([xX][mM][lL][^?<>\s]*) ) captures: 1: punctuation.definition.tag.begin.xml 2: entity.name.tag.xml 3: invalid.illegal.bad-tag-name.xml push: - meta_scope: meta.tag.preprocessor.xml - include: preprocessor-end - include: tag-end-missing-pop - include: tag-attribute # Processing instructions like # meta tag without internal highlighting - match: (<\?)({{name}})\b captures: 1: punctuation.definition.tag.begin.xml 2: entity.name.tag.xml push: - meta_scope: meta.tag.preprocessor.xml - include: preprocessor-end preprocessor-end: - match: \?> scope: punctuation.definition.tag.end.xml pop: true ###[ XML TAGS ]############################################################### tag: # end-tag without attribute support - match: (' scope: punctuation.definition.tag.end.xml pop: true - include: tag-end-missing-pop - match: '[/\?]>' scope: invalid.illegal.bad-tag-end.xml pop: true - match: \S scope: invalid.illegal.unexpected-attribute.xml # opening maybe self-closing tag with optional attributes - match: (<){{qualified_tag_name}} captures: 1: punctuation.definition.tag.begin.xml 2: entity.name.tag.namespace.xml 3: invalid.illegal.bad-tag-name.xml 4: entity.name.tag.xml punctuation.separator.namespace.xml 5: entity.name.tag.localname.xml 6: invalid.illegal.bad-tag-name.xml push: - meta_scope: meta.tag.xml - match: /?> scope: punctuation.definition.tag.end.xml pop: true - match: \?> scope: invalid.illegal.bad-tag-end.xml pop: true - include: tag-end-missing-pop - include: tag-attribute tag-attribute: - match: '{{qualified_attribute_name}}' captures: 1: entity.other.attribute-name.namespace.xml 2: invalid.illegal.bad-attribute-name.xml 3: entity.other.attribute-name.xml punctuation.separator.namespace.xml 4: entity.other.attribute-name.localname.xml 5: invalid.illegal.bad-attribute-name.xml push: tag-attribute-separator-key-value tag-attribute-separator-key-value: - match: '=' scope: punctuation.separator.key-value.xml set: - include: string-quoted-pop - match: '[^?/<>\s]+' scope: invalid.illegal.bad-attribute-value.xml pop: true - include: tag-else-pop - include: tag-else-pop tag-else-pop: # pop, if nothing else matched and ensure `tag-end-missing-pop` works - match: (?=\s?<|\S) pop: true tag-end-missing-pop: # pop, if the next opening tag is following, while scoping the # preceding space to give a hint about the unclosed tag - match: \s?(?=<) scope: invalid.illegal.missing-tag-end.xml pop: true ###[ CONSTANTS ]############################################################## entity: - match: (&#[xX])\h+(;) scope: constant.character.entity.hexadecimal.xml captures: 1: punctuation.definition.entity.xml 2: punctuation.terminator.entity.xml - match: (&#)[0-9]+(;) scope: constant.character.entity.decimal.xml captures: 1: punctuation.definition.entity.xml 2: punctuation.terminator.entity.xml - match: (&){{name}}(;) scope: constant.character.entity.named.xml captures: 1: punctuation.definition.entity.xml 2: punctuation.terminator.entity.xml should-be-entity: - match: '&' scope: invalid.illegal.bad-ampersand.xml - match: '<' scope: invalid.illegal.missing-entity.xml string-unquoted: - match: '{{name}}' scope: string.unquoted.xml string-unquoted-pop: - match: '{{name}}' scope: string.unquoted.xml pop: true string-quoted: - include: string-double-quoted - include: string-single-quoted string-quoted-pop: - include: string-double-quoted-pop - include: string-single-quoted-pop string-double-quoted: - match: '"' scope: punctuation.definition.string.begin.xml push: string-double-quoted-body string-double-quoted-pop: - match: '"' scope: punctuation.definition.string.begin.xml set: string-double-quoted-body string-double-quoted-body: - meta_scope: string.quoted.double.xml - match: '"' scope: punctuation.definition.string.end.xml pop: true - include: tag-end-missing-pop - include: entity - include: should-be-entity string-single-quoted: - match: "'" scope: punctuation.definition.string.begin.xml push: string-single-quoted-body string-single-quoted-pop: - match: "'" scope: punctuation.definition.string.begin.xml set: string-single-quoted-body string-single-quoted-body: - meta_scope: string.quoted.single.xml - match: "'" scope: punctuation.definition.string.end.xml pop: true - include: tag-end-missing-pop - include: entity - include: should-be-entity