use std::char; use nom::{ branch::{alt, permutation}, bytes::streaming::{is_a, is_not, tag, tag_no_case, take, take_till, take_until, take_while}, character::{ complete::one_of, streaming::{alpha1, char, digit1, none_of, satisfy}, }, combinator::{cond, map, map_parser, map_res, not, opt, peek, recognize, value, verify}, error::{Error, ErrorKind}, multi::{many0, many1, many_till}, sequence::{delimited, pair, preceded, separated_pair, terminated, tuple}, Err, IResult, Parser, }; // parser: parses tokens from lexer into events // no well formedness, validity, or data model, simple translation of input into rust types // output is a rust representation of the input xml // types could be used for xml production too? enum ContentItem<'s> { CharData(&'s str), Element(Element<'s>), // Reference(Reference<'s>), // CDSect(CDSect<'s>), } type Content<'s> = Option>>; type Document<'s> = (Prolog<'s>, Element<'s>, Vec>); /// [1] document ::= prolog element Misc* pub fn document(input: &str) -> IResult<&str, Document> { tuple((prolog, element, many0(misc)))(input) } type Char = char; /// [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */ pub fn xmlchar(input: &str) -> IResult<&str, Char> { satisfy( |c| matches!(c, '\u{9}' | '\u{A}' | '\u{D}' | '\u{20}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..='\u{10FFFF}'), )(input) } type S<'s> = &'s str; /// [3] S ::= (#x20 | #x9 | #xD | #xA)+ pub fn s(input: &str) -> IResult<&str, S> { is_a("\u{20}\u{9}\u{D}\u{A}")(input) } type NameStartChar = char; /// [4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] pub fn name_start_char(input: &str) -> IResult<&str, NameStartChar> { satisfy( |c| matches!(c, ':' | 'A'..='Z' | '_' | 'a'..='z' | '\u{C0}'..='\u{D6}' | '\u{D8}'..='\u{F6}' | '\u{F8}'..='\u{2FF}' | '\u{370}'..='\u{37D}' | '\u{37F}'..='\u{1FFF}' | '\u{200C}'..='\u{200D}' | '\u{2070}'..='\u{218F}' | '\u{2C00}'..='\u{2FEF}' | '\u{3001}'..='\u{D7FF}' | '\u{F900}'..='\u{FDCF}' | '\u{FDF0}'..='\u{FFFD}' | '\u{10000}'..='\u{EFFFF}'), )(input) } type NameChar = char; /// [4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] pub fn name_char(input: &str) -> IResult<&str, NameChar> { alt(( name_start_char, satisfy( |c| matches!(c, '-' | '.' | '0'..='9' | '\u{B7}' | '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}'), ), ))(input) } type Name<'s> = &'s str; /// [5] Name ::= NameStartChar (NameChar)* pub fn name(input: &str) -> IResult<&str, Name> { recognize(pair(name_start_char, many0(name_char)))(input) } type Names<'s> = &'s str; /// [6] Names ::= Name (#x20 Name)* pub fn names(input: &str) -> IResult<&str, Names> { recognize(pair(name, many0(pair(char('\u{20}'), name))))(input) } type Nmtoken<'s> = &'s str; /// [7] Nmtoken ::= (NameChar)+ pub fn nmtoken(input: &str) -> IResult<&str, Nmtoken> { recognize(many1(name_char))(input) } type Nmtokens<'s> = &'s str; /// [8] Nmtokens ::= Nmtoken (#x20 Nmtoken)* pub fn nmtokens(input: &str) -> IResult<&str, Nmtokens> { recognize(pair(nmtoken, many0(pair(char('\u{20}'), nmtoken))))(input) } enum LiteralData<'s> { String(&'s str), PEReference(PEReference<'s>), Reference(Reference<'s>), } type EntityValue<'s> = Vec>; /// [9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' /// | "'" ([^%&'] | PEReference | Reference)* "'" pub fn entity_value(input: &str) -> IResult<&str, EntityValue> { alt(( delimited( char('"'), many0(alt(( map( recognize(many_till(take(1usize), peek(one_of("%&\"")))), |string| LiteralData::String(string), ), map(pe_reference, |pe_reference| { LiteralData::PEReference(pe_reference) }), map(reference, |reference| LiteralData::Reference(reference)), ))), char('"'), ), delimited( char('\''), many0(alt(( map( recognize(many_till(take(1usize), peek(one_of("%&'")))), |string| LiteralData::String(string), ), map(pe_reference, |pe_reference| { LiteralData::PEReference(pe_reference) }), map(reference, |reference| LiteralData::Reference(reference)), ))), char('\''), ), ))(input) } type AttValue<'s> = Vec>; /// [10] AttValue ::= '"' ([^<&"] | Reference)* '"' /// | "'" ([^<&'] | Reference)* "'" pub fn att_value(input: &str) -> IResult<&str, AttValue> { alt(( delimited( char('"'), many0(alt(( map( recognize(many_till(take(1usize), peek(one_of("%&\"")))), |string| LiteralData::String(string), ), map(reference, |reference| LiteralData::Reference(reference)), ))), char('"'), ), delimited( char('\''), many0(alt(( map( recognize(many_till(take(1usize), peek(one_of("%&'")))), |string| LiteralData::String(string), ), map(reference, |reference| LiteralData::Reference(reference)), ))), char('\''), ), ))(input) } type SystemLiteral<'s> = &'s str; /// [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") pub fn system_literal(input: &str) -> IResult<&str, SystemLiteral> { alt(( delimited(char('"'), recognize(many0(none_of("\""))), char('"')), delimited(char('\''), recognize(many0(none_of("'"))), char('\'')), ))(input) } type PubidLiteral<'s> = &'s str; /// [12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" pub fn pubid_literal(input: &str) -> IResult<&str, PubidLiteral> { alt(( delimited(char('"'), recognize(many0(pubid_char)), char('"')), delimited( char('\''), recognize(many0(recognize(not(char('\''))).and_then(pubid_char))), char('\''), ), ))(input) } type PubidChar<'s> = char; /// [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] pub fn pubid_char(input: &str) -> IResult<&str, PubidChar> { satisfy(|c| matches!(c, '\u{20}' | '\u{D}' | '\u{A}' | 'a'..='z' | 'A'..='Z' | '0'..='9'))( input, ) } type CharData<'s> = &'s str; /// [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) pub fn char_data(input: &str) -> IResult<&str, CharData> { recognize(many_till( none_of("<&"), peek(alt((recognize(one_of("<&")), tag("]]>")))), ))(input) // let tagg: &str; // if let Ok((_, tagg1)) = peek(take_until::<&str, &str, Error<&str>>("]]>"))(input) { // if let Ok((_, tagg2)) = // peek::<&str, &str, Error<&str>, _>(take_till(|c: char| c == '<' || c == '&'))(input) // { // if tagg1.len() < tagg2.len() { // tagg = tagg1 // } else { // tagg = tagg2 // } // } else { // tagg = tagg1; // } // } else { // (_, tagg) = peek(take_till(|c| c == '<' || c == '&'))(input)? // } // tag(tagg)(input) // recognize(many0(permutation((none_of("<&"), not(tag("]]>"))))))(input) // recognize(many0(not(alt((tag("<"), tag("&"), tag("]]>"))))))(input) // take_till(|c| c == '<' || c == '&').and_then(take_until("]]>"))(input) } type Comment<'s> = &'s str; /// Comment ::= '' pub fn comment(input: &str) -> IResult<&str, Comment> { delimited( tag(""), )(input) } #[derive(Clone)] struct PI<'s> { target: &'s str, instruction: Option<&'s str>, } /// [16] PI ::= '' Char*)))? '?>' pub fn pi(input: &str) -> IResult<&str, PI> { map( delimited( tag("")))))), ), tag("?>"), ), |(target, instruction)| PI { target, instruction, }, )(input) } type PITarget<'s> = &'s str; /// [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) pub fn pi_target(input: &str) -> IResult<&str, PITarget> { let (rest, name) = name(input)?; if name.to_lowercase() == "xml" { return Err(Err::Error(Error { input, // TODO: check if better error to return code: ErrorKind::Tag, })); } else { return Ok((rest, name)); } } type CDSect<'s> = (CDStart<'s>, CData<'s>, CDEnd<'s>); /// [18] CDSect ::= CDStart CData CDEnd pub fn cd_sect(input: &str) -> IResult<&str, CDSect> { tuple((cd_start, cdata, cd_end))(input) } type CDStart<'s> = &'s str; /// [19] CDStart ::= ' IResult<&str, CDStart> { tag(" = &'s str; /// [20] CData ::= (Char* - (Char* ']]>' Char*)) pub fn cdata(input: &str) -> IResult<&str, CData> { recognize(many_till(xmlchar, peek(tag("]]>"))))(input) } type CDEnd<'s> = &'s str; /// [21] CDEnd ::= ']]>' pub fn cd_end(input: &str) -> IResult<&str, CDEnd> { tag("]]>")(input) } type Prolog<'s> = ( Option>, Vec>, Option<(DoctypeDecl<'s>, Vec>)>, ); /// [22] prolog ::= XMLDecl? Misc* (doctypedecl Misc*)? pub fn prolog(input: &str) -> IResult<&str, Prolog> { tuple(( opt(xml_decl), many0(misc), opt(tuple((doctypedecl, many0(misc)))), ))(input) } struct XMLDecl<'s> { version_info: VersionInfo, encoding_decl: Option>, sd_decl: Option, } /// [23] XMLDecl ::= '' pub fn xml_decl(input: &str) -> IResult<&str, XMLDecl> { map( delimited( tag("")), ), |(version_info, encoding_decl, sd_decl)| XMLDecl { version_info, encoding_decl, sd_decl, }, )(input) } type VersionInfo = VersionNum; /// [24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"') pub fn version_info(input: &str) -> IResult<&str, VersionInfo> { preceded( tuple((s, tag("version"), eq)), alt(( delimited(char('\''), version_num, char('\'')), delimited(char('"'), version_num, char('"')), )), )(input) } /// [25] Eq ::= S? '=' S? pub fn eq(input: &str) -> IResult<&str, &str> { recognize(tuple((opt(s), char('='), opt(s))))(input) } #[derive(Clone)] enum VersionNum { One, OneDotOne, } /// [26] VersionNum ::= '1.' [0-9]+ pub fn version_num(input: &str) -> IResult<&str, VersionNum> { preceded( tag("1."), alt(( value(VersionNum::One, char('0')), value(VersionNum::OneDotOne, char('1')), )), )(input) } #[derive(Clone)] enum Misc<'s> { Comment(Comment<'s>), PI(PI<'s>), // TODO: how to deal with whitespace S, } /// [27] Misc ::= Comment | PI | S pub fn misc(input: &str) -> IResult<&str, Misc> { alt(( map(comment, |comment| Misc::Comment(comment)), map(pi, |pi| Misc::PI(pi)), value(Misc::S, s), ))(input) } struct DoctypeDecl<'s> { name: &'s str, external_id: Option>, int_subset: Option>, } /// [28] doctypedecl ::= '' pub fn doctypedecl(input: &str) -> IResult<&str, DoctypeDecl> { map( delimited( pair(tag(""), ), |(name, external_id, int_subset)| DoctypeDecl { name, external_id, int_subset, }, )(input) } #[derive(Clone)] enum DeclSep<'s> { PEReference(PEReference<'s>), // TODO: tackle whitespace S, } /// [28a] DeclSep ::= PEReference | S pub fn decl_sep(input: &str) -> IResult<&str, DeclSep> { alt(( map(pe_reference, |pe_reference| { DeclSep::PEReference(pe_reference) }), value(DeclSep::S, s), ))(input) } enum IntSubsetDeclaration<'s> { MarkupDecl(MarkupDecl<'s>), DeclSep(DeclSep<'s>), } type IntSubset<'s> = Vec>; /// [28b] intSubset ::= (markupdecl | DeclSep)* pub fn int_subset(input: &str) -> IResult<&str, IntSubset> { many0(alt(( map(markup_decl, |markup_decl| { IntSubsetDeclaration::MarkupDecl(markup_decl) }), map(decl_sep, |decl_sep| IntSubsetDeclaration::DeclSep(decl_sep)), )))(input) } enum MarkupDecl<'s> { ElementDecl(ElementDecl<'s>), AttlistDecl(AttlistDecl<'s>), EntityDecl(EntityDecl<'s>), NotationDecl(NotationDecl<'s>), PI(PI<'s>), Comment(Comment<'s>), } /// [29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment pub fn markup_decl(input: &str) -> IResult<&str, MarkupDecl> { alt(( map(element_decl, |element_decl| { MarkupDecl::ElementDecl(element_decl) }), map(attlist_decl, |attlist_decl| { MarkupDecl::AttlistDecl(attlist_decl) }), map(entity_decl, |entity_decl| { MarkupDecl::EntityDecl(entity_decl) }), map(notation_decl, |notation_decl| { MarkupDecl::NotationDecl(notation_decl) }), map(pi, |pi| MarkupDecl::PI(pi)), map(comment, |comment| MarkupDecl::Comment(comment)), ))(input) } struct ExtSubset<'s> { text_decl: Option>, ext_subset_decl: ExtSubsetDecl<'s>, } /// [30] extSubset ::= TextDecl? extSubsetDecl pub fn ext_subset(input: &str) -> IResult<&str, ExtSubset> { map( pair(opt(text_decl), ext_subset_decl), |(text_decl, ext_subset_decl)| ExtSubset { text_decl, ext_subset_decl, }, )(input) } enum ExtSubsetDeclaration<'s> { MarkupDecl(MarkupDecl<'s>), ConditionalSect(ConditionalSect<'s>), DeclSep(DeclSep<'s>), } type ExtSubsetDecl<'s> = Vec>; /// [31] extSubsetDecl ::= ( markupdecl | conditionalSect | DeclSep)* pub fn ext_subset_decl(input: &str) -> IResult<&str, ExtSubsetDecl> { many0(alt(( map(markup_decl, |markup_decl| { ExtSubsetDeclaration::MarkupDecl(markup_decl) }), map(conditional_sect, |conditional_sect| { ExtSubsetDeclaration::ConditionalSect(conditional_sect) }), map(decl_sep, |decl_sep| ExtSubsetDeclaration::DeclSep(decl_sep)), )))(input) } type SDDecl = bool; /// [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"')) pub fn sd_decl(input: &str) -> IResult<&str, SDDecl> { preceded( tuple((s, tag("standalone"), eq)), alt(( delimited( char('\''), alt((value(true, tag("yes")), value(false, tag("no")))), char('\''), ), delimited( char('"'), alt((value(true, tag("yes")), value(false, tag("no")))), char('"'), ), )), )(input) } // (Productions 33 through 38 have been removed.) enum Element<'s> { Empty(EmptyElemTag<'s>), NotEmpty(STag<'s>, Content<'s>, ETag<'s>), } /// [39] element ::= EmptyElemTag | STag content ETag pub fn element(input: &str) -> IResult<&str, Element> { alt(( empty_elem_tag, map(tuple((s_tag, content, e_tag)), |(start, content, end)| {}), ))(input) } // let STag<'s> = (Name<'s>, ); /// [40] STag ::= '<' Name (S Attribute)* S? '>' type Attribute<'s> = (Name<'s>, AttValue<'s>); /// [41] Attribute ::= Name Eq AttValue pub fn attribute(input: &str) -> IResult<&str, Attribute> { separated_pair(name, eq, att_value)(input) } enum CharRef<'s> { Decimal(&'s str), Hexadecimal(&'s str), } /// [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' pub fn char_ref(input: &str) -> IResult<&str, CharRef> { alt(( delimited( tag("&#"), map(take_while(|c| matches!(c, '0'..='9')), |decimal| { CharRef::Decimal(decimal) }), tag(";"), ), delimited( tag("&#x"), map( take_while(|c| matches!(c, '0'..='9' | 'a'..='f' | 'A'..='F' )), |hexadecimal| CharRef::Hexadecimal(hexadecimal), ), tag(";"), ), ))(input) } enum Reference<'s> { EntityRef(EntityRef<'s>), CharRef(CharRef<'s>), } /// [67] Reference ::= EntityRef | CharRef pub fn reference(input: &str) -> IResult<&str, Reference> { alt(( map(entity_ref, |entity_ref| Reference::EntityRef(entity_ref)), map(char_ref, |char_ref| Reference::CharRef(char_ref)), ))(input) } type EntityRef<'s> = &'s str; /// [68] EntityRef ::= '&' Name ';' pub fn entity_ref(input: &str) -> IResult<&str, EntityRef> { delimited(tag("&"), name, tag(";"))(input) } type PEReference<'s> = &'s str; /// [69] PEReference ::= '%' Name ';' pub fn pe_reference(input: &str) -> IResult<&str, PEReference> { delimited(tag("%"), name, tag(";"))(input) } enum EntityDecl<'s> { GEDecl(GEDecl<'s>), PEDecl(PEDecl<'s>), } /// [70] EntityDecl ::= GEDecl | PEDecl pub fn entity_decl(input: &str) -> IResult<&str, EntityDecl> { alt(( map(ge_decl, |ge_decl| EntityDecl::GEDecl(ge_decl)), map(pe_decl, |pe_decl| EntityDecl::PEDecl(pe_decl)), ))(input) } struct GEDecl<'s> { name: Name<'s>, entity_def: EntityDef<'s>, } /// [71] GEDecl ::= '' pub fn ge_decl(input: &str) -> IResult<&str, GEDecl> { map( delimited( pair(tag("")), ), |(name, entity_def)| GEDecl { name, entity_def }, )(input) } struct PEDecl<'s> { name: Name<'s>, pe_def: PEDef<'s>, } /// [72] PEDecl ::= '' pub fn pe_decl(input: &str) -> IResult<&str, PEDecl> { map( delimited( tuple((tag("")), ), |(name, pe_def)| PEDecl { name, pe_def }, )(input) } enum EntityDef<'s> { EntityValue(EntityValue<'s>), ExternalID { external_id: ExternalID<'s>, ndata_decl: Option>, }, } /// [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) pub fn entity_def(input: &str) -> IResult<&str, EntityDef> { alt(( map(entity_value, |entity_value| { EntityDef::EntityValue(entity_value) }), map( pair(external_id, opt(ndata_decl)), |(external_id, ndata_decl)| EntityDef::ExternalID { external_id, ndata_decl, }, ), ))(input) } enum PEDef<'s> { EntityValue(EntityValue<'s>), ExternalID(ExternalID<'s>), } /// [74] PEDef ::= EntityValue | ExternalID pub fn pe_def(input: &str) -> IResult<&str, PEDef> { alt(( map(entity_value, |entity_value| { PEDef::EntityValue(entity_value) }), map(external_id, |external_id| PEDef::ExternalID(external_id)), ))(input) } enum ExternalID<'s> { SYSTEM { system_identifier: &'s str, }, PUBLIC { public_identifier: &'s str, system_identifier: &'s str, }, } /// [75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral // pub fn external_id(input: &str) -> IResult<&str, ExternalID> { pub fn external_id(input: &str) -> IResult<&str, ExternalID> { alt(( map( preceded(pair(tag("SYSTEM"), s), system_literal), |system_identifier| ExternalID::SYSTEM { system_identifier }, ), map( preceded( pair(tag("PUBLIC"), s), separated_pair(pubid_literal, s, system_literal), ), |(public_identifier, system_identifier)| ExternalID::PUBLIC { public_identifier, system_identifier, }, ), ))(input) } type NDataDecl<'s> = &'s str; /// [76] NDataDecl ::= S 'NDATA' S Name pub fn ndata_decl(input: &str) -> IResult<&str, NDataDecl> { preceded(tuple((s, tag("NDATA"), s)), name)(input) } struct TextDecl<'s> { version_info: Option, encoding_decl: EncodingDecl<'s>, } /// [77] TextDecl ::= '' pub fn text_decl(input: &str) -> IResult<&str, TextDecl> { map( delimited( tag(""), ), |(version_info, encoding_decl)| TextDecl { version_info, encoding_decl, }, )(input) } struct ExtParsedEnt<'s> { text_decl: Option>, content: Content<'s>, } /// [78] extParsedEnt ::= TextDecl? content pub fn ext_parsed_ent(input: &str) -> IResult<&str, ExtParsedEnt> { pair(opt(text_decl), content)(input) } type EncodingDecl<'s> = EncName<'s>; /// [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName pub fn encoding_decl(input: &str) -> IResult<&str, EncodingDecl> { preceded( tuple((s, tag("encoding"), eq)), alt(( delimited(char('"'), enc_name, char('"')), delimited(char('\''), enc_name, char('\'')), )), )(input) } type EncName<'s> = &'s str; /// [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* pub fn enc_name(input: &str) -> IResult<&str, EncName> { recognize(pair( satisfy(|c| matches!(c, 'A'..='Z' | 'a'..='z' )), many0(satisfy( |c| matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '.' | '_' | '-' ), )), ))(input) } struct NotationDecl<'s> { name: &'s str, id: NotationDeclID<'s>, } enum NotationDeclID<'s> { External(ExternalID<'s>), Public(PublicID<'s>), } /// [82] NotationDecl ::= '' pub fn notation_decl(input: &str) -> IResult<&str, NotationDecl> { map( delimited( pair(tag("")), ), |(name, id)| NotationDecl { name, id }, )(input) } type PublicID<'s> = &'s str; /// [83] PublicID ::= 'PUBLIC' S PubidLiteral pub fn public_id(input: &str) -> IResult<&str, PublicID> { preceded(pair(tag("PUBLIC"), s), pubid_literal)(input) } #[cfg(test)] mod tests { use std::num::NonZero; use super::*; #[test] fn test_char_data() { assert_eq!(Ok(("&def]]>ghi", "abc")), char_data("abc&def]]>ghi")); assert_eq!(Ok(("]]>ghi", "abcdef")), char_data("abcdef]]>ghi")); assert_eq!(Ok(("&defghi", "abc")), char_data("abc&defghi")); assert_eq!(Ok(("]]>def&ghi", "abc")), char_data("abc]]>def&ghi")); assert_eq!(Ok(("&ghi", "abc]>def")), char_data("abc]>def&ghi")); assert_eq!( Err(Err::Incomplete(nom::Needed::Size( NonZero::new(3usize).unwrap() ))), char_data("abcdefghi") ); } #[test] fn test_comment() { assert_eq!(Ok(("", "")), comment("")); assert_eq!(Ok(("", "asdf")), comment("")); assert_eq!(Ok(("", "as-df")), comment("")); assert_eq!( Err(Err::Incomplete(nom::Needed::Size( NonZero::new(2usize).unwrap() ))), comment("