/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
 */

use std::borrow::Cow;

use request::TextMatch;
pub mod property;
pub mod request;
pub mod response;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct NamedElement {
    pub ns: Namespace,
    pub element: Element,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum Namespace {
    Dav,
    CalDav,
    CardDav,
    CalendarServer,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct Namespaces {
    pub(crate) cal: bool,
    pub(crate) card: bool,
    pub(crate) cs: bool,
}

impl Namespaces {
    pub fn set(&mut self, ns: Namespace) {
        match ns {
            Namespace::CalDav => self.cal = true,
            Namespace::CardDav => self.card = true,
            Namespace::CalendarServer => self.cs = true,
            Namespace::Dav => {}
        }
    }
}

impl Namespace {
    pub fn try_parse(value: &[u8]) -> Option<Self> {
        hashify::tiny_map!(value,
            "DAV:" => Namespace::Dav,
            "urn:ietf:params:xml:ns:caldav" => Namespace::CalDav,
            "urn:ietf:params:xml:ns:carddav" => Namespace::CardDav,
            "http://calendarserver.org/ns/" => Namespace::CalendarServer,
            "http://calendarserver.org/ns" => Namespace::CalendarServer
        )
    }

    pub fn prefix(&self) -> &str {
        match self {
            Namespace::Dav => "D",
            Namespace::CalDav => "A",
            Namespace::CardDav => "B",
            Namespace::CalendarServer => "C",
        }
    }

    pub fn namespace(&self) -> &'static str {
        match self {
            Namespace::Dav => "DAV:",
            Namespace::CalDav => "urn:ietf:params:xml:ns:caldav",
            Namespace::CardDav => "urn:ietf:params:xml:ns:carddav",
            Namespace::CalendarServer => "http://calendarserver.org/ns/",
        }
    }
}

impl AsRef<str> for Namespace {
    fn as_ref(&self) -> &str {
        self.namespace()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum Element {
    Abstract,
    Ace,
    Acl,
    AclPrincipalPropSet,
    AclRestrictions,
    Activelock,
    ActivityCheckoutSet,
    ActivityCollectionSet,
    ActivitySet,
    ActivityVersionSet,
    Add,
    AddMember,
    AddedVersion,
    AddressData,
    AddressDataType,
    Addressbook,
    AddressbookDescription,
    AddressbookHomeSet,
    AddressbookMultiget,
    AddressbookQuery,
    After,
    All,
    Allcomp,
    AllowClientDefinedUri,
    AllowedAttendeeSchedulingObjectChange,
    AllowedOrganizerSchedulingObjectChange,
    AllowedPrincipal,
    Allprop,
    AlternateUriSet,
    And,
    AnyOtherProperty,
    ApplyToVersion,
    ApplyToPrincipalCollectionSet,
    Ascending,
    Authenticated,
    AutoMergeSet,
    AutoUpdate,
    AutoVersion,
    Baseline,
    BaselineCollection,
    BaselineControl,
    BaselineControlResponse,
    BaselineControlledCollection,
    BaselineControlledCollectionSet,
    Basicsearch,
    Basicsearchschema,
    Before,
    Bind,
    BindResponse,
    BindingName,
    Calendar,
    CalendarAvailability,
    CalendarData,
    CalendarDescription,
    CalendarHomeSet,
    CalendarMultiget,
    CalendarQuery,
    CalendarTimezone,
    CalendarTimezoneId,
    CalendarUserAddressSet,
    CalendarUserType,
    Caseless,
    ChangedVersion,
    CheckedIn,
    CheckedOut,
    Checkin,
    CheckinActivity,
    CheckinFork,
    CheckinResponse,
    Checkout,
    CheckoutCheckin,
    CheckoutFork,
    CheckoutResponse,
    CheckoutSet,
    CheckoutUnlockedCheckin,
    Collection,
    Comment,
    CommonAncestor,
    Comp,
    CompFilter,
    CompareBaseline,
    CompareBaselineReport,
    ConflictPreview,
    Contains,
    Creationdate,
    CreatorDisplayname,
    CurrentActivitySet,
    CurrentUserPrincipal,
    CurrentUserPrivilegeSet,
    CurrentWorkspaceSet,
    Datatype,
    DefaultCalendarNeeded,
    DeletedVersion,
    Deny,
    DenyBeforeGrant,
    Depth,
    Descending,
    Description,
    Discouraged,
    Displayname,
    Eq,
    Error,
    Exclusive,
    Expand,
    ExpandProperty,
    Filter,
    First,
    Forbidden,
    ForkOk,
    FreeBusyQuery,
    From,
    Getcontentlanguage,
    Getcontentlength,
    Getcontenttype,
    Getctag,
    Getetag,
    Getlastmodified,
    Grammar,
    Grant,
    GrantOnly,
    Group,
    GroupMemberSet,
    GroupMembership,
    Gt,
    Gte,
    Href,
    IgnorePreview,
    Include,
    IncludeVersions,
    Inherited,
    InheritedAclSet,
    Invert,
    IsCollection,
    IsDefined,
    IsNotDefined,
    KeepCheckedOut,
    Label,
    LabelName,
    LabelNameSet,
    LabelResponse,
    LanguageDefined,
    LanguageMatches,
    Last,
    LatestActivityVersion,
    LatestActivityVersionReport,
    Like,
    Limit,
    LimitFreebusySet,
    LimitRecurrenceSet,
    LimitedNumberOfAces,
    Literal,
    LocateByHistory,
    Location,
    LockTokenSubmitted,
    Lockdiscovery,
    LockedCheckout,
    Lockentry,
    Lockinfo,
    Lockroot,
    Lockscope,
    Locktoken,
    Locktype,
    Lt,
    Lte,
    ManagedAttachmentsServerUrl,
    Match,
    MaxAttachmentSize,
    MaxAttachmentsPerResource,
    MaxAttendeesPerInstance,
    MaxDateTime,
    MaxInstances,
    MaxResourceSize,
    Merge,
    MergePreview,
    MergePreviewReport,
    MergeSet,
    MinDateTime,
    MissingRequiredPrincipal,
    Mkactivity,
    MkactivityResponse,
    Mkcalendar,
    MkcalendarResponse,
    Mkcol,
    MkcolResponse,
    Mkredirectref,
    MkredirectrefResponse,
    Mkworkspace,
    MkworkspaceResponse,
    Mount,
    Multistatus,
    NeedPrivileges,
    New,
    NoAbstract,
    NoAceConflict,
    NoAutoMerge,
    NoCheckout,
    NoConflictingLock,
    NoInheritedAceConflict,
    NoInvert,
    NoProtectedAceConflict,
    NoUidConflict,
    Not,
    NotSupportedPrivilege,
    Nresults,
    Opaque,
    Opdesc,
    Open,
    OperandLiteral,
    OperandProperty,
    OperandTypedLiteral,
    Operators,
    Options,
    OptionsResponse,
    Or,
    Order,
    OrderMember,
    Orderby,
    OrderingType,
    Orderpatch,
    OrderpatchResponse,
    Owner,
    ParamFilter,
    Parent,
    ParentSet,
    Permanent,
    Position,
    PredecessorSet,
    Principal,
    PrincipalUrl,
    PrincipalAddress,
    PrincipalCollectionSet,
    PrincipalMatch,
    PrincipalProperty,
    PrincipalPropertySearch,
    PrincipalSearchProperty,
    PrincipalSearchPropertySet,
    Privilege,
    Prop,
    PropFilter,
    Propdesc,
    Properties,
    Property,
    PropertySearch,
    Propertyupdate,
    Propfind,
    Propname,
    Propstat,
    Protected,
    QuerySchema,
    QuerySchemaDiscovery,
    QuotaAvailableBytes,
    QuotaUsedBytes,
    Read,
    ReadAcl,
    ReadCurrentUserPrivilegeSet,
    ReadFreeBusy,
    Rebind,
    RebindResponse,
    Recipient,
    RecognizedPrincipal,
    RedirectLifetime,
    Redirectref,
    Reftarget,
    Remove,
    Report,
    RequestStatus,
    RequiredPrincipal,
    Resource,
    ResourceId,
    Resourcetype,
    Response,
    Responsedescription,
    RootVersion,
    SameOrganizerInAllComponents,
    ScheduleCalendarTransp,
    ScheduleDefaultCalendarUrl,
    ScheduleDeliver,
    ScheduleDeliverInvite,
    ScheduleDeliverReply,
    ScheduleInbox,
    ScheduleInboxUrl,
    ScheduleOutbox,
    ScheduleOutboxUrl,
    ScheduleQueryFreebusy,
    ScheduleResponse,
    ScheduleSend,
    ScheduleSendFreebusy,
    ScheduleSendInvite,
    ScheduleSendReply,
    ScheduleTag,
    Scope,
    Score,
    Searchable,
    Segment,
    Select,
    Selectable,
    Self_,
    Set,
    Shared,
    Sortable,
    Source,
    Status,
    SubactivitySet,
    SubbaselineSet,
    SuccessorSet,
    SupportedAddressData,
    SupportedCalendarComponentSet,
    SupportedCalendarData,
    SupportedCollation,
    SupportedCollationSet,
    SupportedFilter,
    SupportedLiveProperty,
    SupportedLivePropertySet,
    SupportedMethod,
    SupportedMethodSet,
    SupportedPrivilege,
    SupportedPrivilegeSet,
    SupportedQueryGrammar,
    SupportedQueryGrammarSet,
    SupportedReport,
    SupportedReportSet,
    SupportedRscale,
    SupportedRscaleSet,
    Supportedlock,
    SyncCollection,
    SyncLevel,
    SyncToken,
    Target,
    Temporary,
    TextMatch,
    TimeRange,
    Timeout,
    Timezone,
    TimezoneId,
    TimezoneServiceSet,
    Transparent,
    TypedLiteral,
    Unauthenticated,
    Unbind,
    UnbindResponse,
    Uncheckout,
    UncheckoutResponse,
    UniqueSchedulingObjectResource,
    Unlock,
    Unreserved,
    Update,
    UpdatePreview,
    Updateredirectref,
    UpdateredirectrefResponse,
    Url,
    Username,
    ValidOrganizer,
    ValidScheduleDefaultCalendarUrl,
    ValidSchedulingMessage,
    Version,
    VersionControl,
    VersionControlResponse,
    VersionControlledBinding,
    VersionControlledBindingSet,
    VersionControlledConfiguration,
    VersionHistory,
    VersionHistoryCollectionSet,
    VersionHistorySet,
    VersionName,
    VersionSet,
    VersionTree,
    Where,
    Workspace,
    WorkspaceCheckoutSet,
    WorkspaceCollectionSet,
    Write,
    WriteAcl,
    WriteContent,
    WriteProperties,
}

impl Element {
    pub fn try_parse(value: &[u8]) -> Option<&Self> {
        hashify::map!(value,
            Element,
            "abstract" => Element::Abstract,
            "ace" => Element::Ace,
            "acl" => Element::Acl,
            "acl-principal-prop-set" => Element::AclPrincipalPropSet,
            "acl-restrictions" => Element::AclRestrictions,
            "activelock" => Element::Activelock,
            "activity-checkout-set" => Element::ActivityCheckoutSet,
            "activity-collection-set" => Element::ActivityCollectionSet,
            "activity-set" => Element::ActivitySet,
            "activity-version-set" => Element::ActivityVersionSet,
            "add" => Element::Add,
            "add-member" => Element::AddMember,
            "added-version" => Element::AddedVersion,
            "address-data" => Element::AddressData,
            "address-data-type" => Element::AddressDataType,
            "addressbook" => Element::Addressbook,
            "addressbook-description" => Element::AddressbookDescription,
            "addressbook-home-set" => Element::AddressbookHomeSet,
            "addressbook-multiget" => Element::AddressbookMultiget,
            "addressbook-query" => Element::AddressbookQuery,
            "after" => Element::After,
            "all" => Element::All,
            "allcomp" => Element::Allcomp,
            "allow-client-defined-uri" => Element::AllowClientDefinedUri,
            "allowed-attendee-scheduling-object-change" => Element::AllowedAttendeeSchedulingObjectChange,
            "allowed-organizer-scheduling-object-change" => Element::AllowedOrganizerSchedulingObjectChange,
            "allowed-principal" => Element::AllowedPrincipal,
            "allprop" => Element::Allprop,
            "alternate-URI-set" => Element::AlternateUriSet,
            "and" => Element::And,
            "any-other-property" => Element::AnyOtherProperty,
            "apply-to-version" => Element::ApplyToVersion,
            "apply-to-principal-collection-set" => Element::ApplyToPrincipalCollectionSet,
            "ascending" => Element::Ascending,
            "authenticated" => Element::Authenticated,
            "auto-merge-set" => Element::AutoMergeSet,
            "auto-update" => Element::AutoUpdate,
            "auto-version" => Element::AutoVersion,
            "baseline" => Element::Baseline,
            "baseline-collection" => Element::BaselineCollection,
            "baseline-control" => Element::BaselineControl,
            "baseline-control-response" => Element::BaselineControlResponse,
            "baseline-controlled-collection" => Element::BaselineControlledCollection,
            "baseline-controlled-collection-set" => Element::BaselineControlledCollectionSet,
            "basicsearch" => Element::Basicsearch,
            "basicsearchschema" => Element::Basicsearchschema,
            "before" => Element::Before,
            "bind" => Element::Bind,
            "bind-response" => Element::BindResponse,
            "binding-name" => Element::BindingName,
            "calendar" => Element::Calendar,
            "calendar-availability" => Element::CalendarAvailability,
            "calendar-data" => Element::CalendarData,
            "calendar-description" => Element::CalendarDescription,
            "calendar-home-set" => Element::CalendarHomeSet,
            "calendar-multiget" => Element::CalendarMultiget,
            "calendar-query" => Element::CalendarQuery,
            "calendar-timezone" => Element::CalendarTimezone,
            "calendar-timezone-id" => Element::CalendarTimezoneId,
            "calendar-user-address-set" => Element::CalendarUserAddressSet,
            "calendar-user-type" => Element::CalendarUserType,
            "caseless" => Element::Caseless,
            "changed-version" => Element::ChangedVersion,
            "checked-in" => Element::CheckedIn,
            "checked-out" => Element::CheckedOut,
            "checkin" => Element::Checkin,
            "checkin-activity" => Element::CheckinActivity,
            "checkin-fork" => Element::CheckinFork,
            "checkin-response" => Element::CheckinResponse,
            "checkout" => Element::Checkout,
            "checkout-checkin" => Element::CheckoutCheckin,
            "checkout-fork" => Element::CheckoutFork,
            "checkout-response" => Element::CheckoutResponse,
            "checkout-set" => Element::CheckoutSet,
            "checkout-unlocked-checkin" => Element::CheckoutUnlockedCheckin,
            "collection" => Element::Collection,
            "comment" => Element::Comment,
            "common-ancestor" => Element::CommonAncestor,
            "comp" => Element::Comp,
            "comp-filter" => Element::CompFilter,
            "compare-baseline" => Element::CompareBaseline,
            "compare-baseline-report" => Element::CompareBaselineReport,
            "conflict-preview" => Element::ConflictPreview,
            "contains" => Element::Contains,
            "creationdate" => Element::Creationdate,
            "creator-displayname" => Element::CreatorDisplayname,
            "current-activity-set" => Element::CurrentActivitySet,
            "current-user-principal" => Element::CurrentUserPrincipal,
            "current-user-privilege-set" => Element::CurrentUserPrivilegeSet,
            "current-workspace-set" => Element::CurrentWorkspaceSet,
            "datatype" => Element::Datatype,
            "default-calendar-needed" => Element::DefaultCalendarNeeded,
            "deleted-version" => Element::DeletedVersion,
            "deny" => Element::Deny,
            "deny-before-grant" => Element::DenyBeforeGrant,
            "depth" => Element::Depth,
            "descending" => Element::Descending,
            "description" => Element::Description,
            "discouraged" => Element::Discouraged,
            "displayname" => Element::Displayname,
            "eq" => Element::Eq,
            "error" => Element::Error,
            "exclusive" => Element::Exclusive,
            "expand" => Element::Expand,
            "expand-property" => Element::ExpandProperty,
            "filter" => Element::Filter,
            "first" => Element::First,
            "forbidden" => Element::Forbidden,
            "fork-ok" => Element::ForkOk,
            "free-busy-query" => Element::FreeBusyQuery,
            "from" => Element::From,
            "getcontentlanguage" => Element::Getcontentlanguage,
            "getcontentlength" => Element::Getcontentlength,
            "getcontenttype" => Element::Getcontenttype,
            "getetag" => Element::Getetag,
            "getctag" => Element::Getctag,
            "getlastmodified" => Element::Getlastmodified,
            "grammar" => Element::Grammar,
            "grant" => Element::Grant,
            "grant-only" => Element::GrantOnly,
            "group" => Element::Group,
            "group-member-set" => Element::GroupMemberSet,
            "group-membership" => Element::GroupMembership,
            "gt" => Element::Gt,
            "gte" => Element::Gte,
            "href" => Element::Href,
            "ignore-preview" => Element::IgnorePreview,
            "include" => Element::Include,
            "include-versions" => Element::IncludeVersions,
            "inherited" => Element::Inherited,
            "inherited-acl-set" => Element::InheritedAclSet,
            "invert" => Element::Invert,
            "is-collection" => Element::IsCollection,
            "is-defined" => Element::IsDefined,
            "is-not-defined" => Element::IsNotDefined,
            "keep-checked-out" => Element::KeepCheckedOut,
            "label" => Element::Label,
            "label-name" => Element::LabelName,
            "label-name-set" => Element::LabelNameSet,
            "label-response" => Element::LabelResponse,
            "language-defined" => Element::LanguageDefined,
            "language-matches" => Element::LanguageMatches,
            "last" => Element::Last,
            "latest-activity-version" => Element::LatestActivityVersion,
            "latest-activity-version-report" => Element::LatestActivityVersionReport,
            "like" => Element::Like,
            "limit" => Element::Limit,
            "limit-freebusy-set" => Element::LimitFreebusySet,
            "limit-recurrence-set" => Element::LimitRecurrenceSet,
            "limited-number-of-aces" => Element::LimitedNumberOfAces,
            "literal" => Element::Literal,
            "locate-by-history" => Element::LocateByHistory,
            "location" => Element::Location,
            "lock-token-submitted" => Element::LockTokenSubmitted,
            "lockdiscovery" => Element::Lockdiscovery,
            "locked-checkout" => Element::LockedCheckout,
            "lockentry" => Element::Lockentry,
            "lockinfo" => Element::Lockinfo,
            "lockroot" => Element::Lockroot,
            "lockscope" => Element::Lockscope,
            "locktoken" => Element::Locktoken,
            "locktype" => Element::Locktype,
            "lt" => Element::Lt,
            "lte" => Element::Lte,
            "managed-attachments-server-URL" => Element::ManagedAttachmentsServerUrl,
            "match" => Element::Match,
            "max-attachment-size" => Element::MaxAttachmentSize,
            "max-attachments-per-resource" => Element::MaxAttachmentsPerResource,
            "max-attendees-per-instance" => Element::MaxAttendeesPerInstance,
            "max-date-time" => Element::MaxDateTime,
            "max-instances" => Element::MaxInstances,
            "max-resource-size" => Element::MaxResourceSize,
            "merge" => Element::Merge,
            "merge-preview" => Element::MergePreview,
            "merge-preview-report" => Element::MergePreviewReport,
            "merge-set" => Element::MergeSet,
            "min-date-time" => Element::MinDateTime,
            "missing-required-principal" => Element::MissingRequiredPrincipal,
            "mkactivity" => Element::Mkactivity,
            "mkactivity-response" => Element::MkactivityResponse,
            "mkcalendar" => Element::Mkcalendar,
            "mkcalendar-response" => Element::MkcalendarResponse,
            "mkcol" => Element::Mkcol,
            "mkcol-response" => Element::MkcolResponse,
            "mkredirectref" => Element::Mkredirectref,
            "mkredirectref-response" => Element::MkredirectrefResponse,
            "mkworkspace" => Element::Mkworkspace,
            "mkworkspace-response" => Element::MkworkspaceResponse,
            "mount" => Element::Mount,
            "multistatus" => Element::Multistatus,
            "need-privileges" => Element::NeedPrivileges,
            "new" => Element::New,
            "no-abstract" => Element::NoAbstract,
            "no-ace-conflict" => Element::NoAceConflict,
            "no-auto-merge" => Element::NoAutoMerge,
            "no-checkout" => Element::NoCheckout,
            "no-conflicting-lock" => Element::NoConflictingLock,
            "no-inherited-ace-conflict" => Element::NoInheritedAceConflict,
            "no-invert" => Element::NoInvert,
            "no-protected-ace-conflict" => Element::NoProtectedAceConflict,
            "no-uid-conflict" => Element::NoUidConflict,
            "not" => Element::Not,
            "not-supported-privilege" => Element::NotSupportedPrivilege,
            "nresults" => Element::Nresults,
            "opaque" => Element::Opaque,
            "opdesc" => Element::Opdesc,
            "open" => Element::Open,
            "operand-literal" => Element::OperandLiteral,
            "operand-property" => Element::OperandProperty,
            "operand-typed-literal" => Element::OperandTypedLiteral,
            "operators" => Element::Operators,
            "options" => Element::Options,
            "options-response" => Element::OptionsResponse,
            "or" => Element::Or,
            "order" => Element::Order,
            "order-member" => Element::OrderMember,
            "orderby" => Element::Orderby,
            "ordering-type" => Element::OrderingType,
            "orderpatch" => Element::Orderpatch,
            "orderpatch-response" => Element::OrderpatchResponse,
            "owner" => Element::Owner,
            "param-filter" => Element::ParamFilter,
            "parent" => Element::Parent,
            "parent-set" => Element::ParentSet,
            "permanent" => Element::Permanent,
            "position" => Element::Position,
            "predecessor-set" => Element::PredecessorSet,
            "principal" => Element::Principal,
            "principal-URL" => Element::PrincipalUrl,
            "principal-address" => Element::PrincipalAddress,
            "principal-collection-set" => Element::PrincipalCollectionSet,
            "principal-match" => Element::PrincipalMatch,
            "principal-property" => Element::PrincipalProperty,
            "principal-property-search" => Element::PrincipalPropertySearch,
            "principal-search-property" => Element::PrincipalSearchProperty,
            "principal-search-property-set" => Element::PrincipalSearchPropertySet,
            "privilege" => Element::Privilege,
            "prop" => Element::Prop,
            "prop-filter" => Element::PropFilter,
            "propdesc" => Element::Propdesc,
            "properties" => Element::Properties,
            "property" => Element::Property,
            "property-search" => Element::PropertySearch,
            "propertyupdate" => Element::Propertyupdate,
            "propfind" => Element::Propfind,
            "propname" => Element::Propname,
            "propstat" => Element::Propstat,
            "protected" => Element::Protected,
            "query-schema" => Element::QuerySchema,
            "query-schema-discovery" => Element::QuerySchemaDiscovery,
            "quota-available-bytes" => Element::QuotaAvailableBytes,
            "quota-used-bytes" => Element::QuotaUsedBytes,
            "read" => Element::Read,
            "read-acl" => Element::ReadAcl,
            "read-current-user-privilege-set" => Element::ReadCurrentUserPrivilegeSet,
            "read-free-busy" => Element::ReadFreeBusy,
            "rebind" => Element::Rebind,
            "rebind-response" => Element::RebindResponse,
            "recipient" => Element::Recipient,
            "recognized-principal" => Element::RecognizedPrincipal,
            "redirect-lifetime" => Element::RedirectLifetime,
            "redirectref" => Element::Redirectref,
            "reftarget" => Element::Reftarget,
            "remove" => Element::Remove,
            "report" => Element::Report,
            "request-status" => Element::RequestStatus,
            "required-principal" => Element::RequiredPrincipal,
            "resource" => Element::Resource,
            "resource-id" => Element::ResourceId,
            "resourcetype" => Element::Resourcetype,
            "response" => Element::Response,
            "responsedescription" => Element::Responsedescription,
            "root-version" => Element::RootVersion,
            "same-organizer-in-all-components" => Element::SameOrganizerInAllComponents,
            "schedule-calendar-transp" => Element::ScheduleCalendarTransp,
            "schedule-default-calendar-URL" => Element::ScheduleDefaultCalendarUrl,
            "schedule-deliver" => Element::ScheduleDeliver,
            "schedule-deliver-invite" => Element::ScheduleDeliverInvite,
            "schedule-deliver-reply" => Element::ScheduleDeliverReply,
            "schedule-inbox" => Element::ScheduleInbox,
            "schedule-inbox-URL" => Element::ScheduleInboxUrl,
            "schedule-outbox" => Element::ScheduleOutbox,
            "schedule-outbox-URL" => Element::ScheduleOutboxUrl,
            "schedule-query-freebusy" => Element::ScheduleQueryFreebusy,
            "schedule-response" => Element::ScheduleResponse,
            "schedule-send" => Element::ScheduleSend,
            "schedule-send-freebusy" => Element::ScheduleSendFreebusy,
            "schedule-send-invite" => Element::ScheduleSendInvite,
            "schedule-send-reply" => Element::ScheduleSendReply,
            "schedule-tag" => Element::ScheduleTag,
            "scope" => Element::Scope,
            "score" => Element::Score,
            "searchable" => Element::Searchable,
            "segment" => Element::Segment,
            "select" => Element::Select,
            "selectable" => Element::Selectable,
            "self" => Element::Self_,
            "set" => Element::Set,
            "shared" => Element::Shared,
            "sortable" => Element::Sortable,
            "source" => Element::Source,
            "status" => Element::Status,
            "subactivity-set" => Element::SubactivitySet,
            "subbaseline-set" => Element::SubbaselineSet,
            "successor-set" => Element::SuccessorSet,
            "supported-address-data" => Element::SupportedAddressData,
            "supported-calendar-component-set" => Element::SupportedCalendarComponentSet,
            "supported-calendar-data" => Element::SupportedCalendarData,
            "supported-collation" => Element::SupportedCollation,
            "supported-collation-set" => Element::SupportedCollationSet,
            "supported-filter" => Element::SupportedFilter,
            "supported-live-property" => Element::SupportedLiveProperty,
            "supported-live-property-set" => Element::SupportedLivePropertySet,
            "supported-method" => Element::SupportedMethod,
            "supported-method-set" => Element::SupportedMethodSet,
            "supported-privilege" => Element::SupportedPrivilege,
            "supported-privilege-set" => Element::SupportedPrivilegeSet,
            "supported-query-grammar" => Element::SupportedQueryGrammar,
            "supported-query-grammar-set" => Element::SupportedQueryGrammarSet,
            "supported-report" => Element::SupportedReport,
            "supported-report-set" => Element::SupportedReportSet,
            "supported-rscale" => Element::SupportedRscale,
            "supported-rscale-set" => Element::SupportedRscaleSet,
            "supportedlock" => Element::Supportedlock,
            "sync-collection" => Element::SyncCollection,
            "sync-level" => Element::SyncLevel,
            "sync-token" => Element::SyncToken,
            "target" => Element::Target,
            "temporary" => Element::Temporary,
            "text-match" => Element::TextMatch,
            "time-range" => Element::TimeRange,
            "timeout" => Element::Timeout,
            "timezone" => Element::Timezone,
            "timezone-id" => Element::TimezoneId,
            "timezone-service-set" => Element::TimezoneServiceSet,
            "transparent" => Element::Transparent,
            "typed-literal" => Element::TypedLiteral,
            "unauthenticated" => Element::Unauthenticated,
            "unbind" => Element::Unbind,
            "unbind-response" => Element::UnbindResponse,
            "uncheckout" => Element::Uncheckout,
            "uncheckout-response" => Element::UncheckoutResponse,
            "unique-scheduling-object-resource" => Element::UniqueSchedulingObjectResource,
            "unlock" => Element::Unlock,
            "unreserved" => Element::Unreserved,
            "update" => Element::Update,
            "update-preview" => Element::UpdatePreview,
            "updateredirectref" => Element::Updateredirectref,
            "updateredirectref-response" => Element::UpdateredirectrefResponse,
            "url" => Element::Url,
            "username" => Element::Username,
            "valid-organizer" => Element::ValidOrganizer,
            "valid-schedule-default-calendar-URL" => Element::ValidScheduleDefaultCalendarUrl,
            "valid-scheduling-message" => Element::ValidSchedulingMessage,
            "version" => Element::Version,
            "version-control" => Element::VersionControl,
            "version-control-response" => Element::VersionControlResponse,
            "version-controlled-binding" => Element::VersionControlledBinding,
            "version-controlled-binding-set" => Element::VersionControlledBindingSet,
            "version-controlled-configuration" => Element::VersionControlledConfiguration,
            "version-history" => Element::VersionHistory,
            "version-history-collection-set" => Element::VersionHistoryCollectionSet,
            "version-history-set" => Element::VersionHistorySet,
            "version-name" => Element::VersionName,
            "version-set" => Element::VersionSet,
            "version-tree" => Element::VersionTree,
            "where" => Element::Where,
            "workspace" => Element::Workspace,
            "workspace-checkout-set" => Element::WorkspaceCheckoutSet,
            "workspace-collection-set" => Element::WorkspaceCollectionSet,
            "write" => Element::Write,
            "write-acl" => Element::WriteAcl,
            "write-content" => Element::WriteContent,
            "write-properties" => Element::WriteProperties,
        )
    }
}

impl AsRef<str> for Element {
    fn as_ref(&self) -> &str {
        match self {
            Element::Abstract => "abstract",
            Element::Ace => "ace",
            Element::Acl => "acl",
            Element::AclPrincipalPropSet => "acl-principal-prop-set",
            Element::AclRestrictions => "acl-restrictions",
            Element::Activelock => "activelock",
            Element::ActivityCheckoutSet => "activity-checkout-set",
            Element::ActivityCollectionSet => "activity-collection-set",
            Element::ActivitySet => "activity-set",
            Element::ActivityVersionSet => "activity-version-set",
            Element::Add => "add",
            Element::AddMember => "add-member",
            Element::AddedVersion => "added-version",
            Element::AddressData => "address-data",
            Element::AddressDataType => "address-data-type",
            Element::Addressbook => "addressbook",
            Element::AddressbookDescription => "addressbook-description",
            Element::AddressbookHomeSet => "addressbook-home-set",
            Element::AddressbookMultiget => "addressbook-multiget",
            Element::AddressbookQuery => "addressbook-query",
            Element::After => "after",
            Element::All => "all",
            Element::Allcomp => "allcomp",
            Element::AllowClientDefinedUri => "allow-client-defined-uri",
            Element::AllowedAttendeeSchedulingObjectChange => {
                "allowed-attendee-scheduling-object-change"
            }
            Element::AllowedOrganizerSchedulingObjectChange => {
                "allowed-organizer-scheduling-object-change"
            }
            Element::AllowedPrincipal => "allowed-principal",
            Element::Allprop => "allprop",
            Element::AlternateUriSet => "alternate-URI-set",
            Element::And => "and",
            Element::AnyOtherProperty => "any-other-property",
            Element::ApplyToVersion => "apply-to-version",
            Element::ApplyToPrincipalCollectionSet => "apply-to-principal-collection-set",
            Element::Ascending => "ascending",
            Element::Authenticated => "authenticated",
            Element::AutoMergeSet => "auto-merge-set",
            Element::AutoUpdate => "auto-update",
            Element::AutoVersion => "auto-version",
            Element::Baseline => "baseline",
            Element::BaselineCollection => "baseline-collection",
            Element::BaselineControl => "baseline-control",
            Element::BaselineControlResponse => "baseline-control-response",
            Element::BaselineControlledCollection => "baseline-controlled-collection",
            Element::BaselineControlledCollectionSet => "baseline-controlled-collection-set",
            Element::Basicsearch => "basicsearch",
            Element::Basicsearchschema => "basicsearchschema",
            Element::Before => "before",
            Element::Bind => "bind",
            Element::BindResponse => "bind-response",
            Element::BindingName => "binding-name",
            Element::Calendar => "calendar",
            Element::CalendarAvailability => "calendar-availability",
            Element::CalendarData => "calendar-data",
            Element::CalendarDescription => "calendar-description",
            Element::CalendarHomeSet => "calendar-home-set",
            Element::CalendarMultiget => "calendar-multiget",
            Element::CalendarQuery => "calendar-query",
            Element::CalendarTimezone => "calendar-timezone",
            Element::CalendarTimezoneId => "calendar-timezone-id",
            Element::CalendarUserAddressSet => "calendar-user-address-set",
            Element::CalendarUserType => "calendar-user-type",
            Element::Caseless => "caseless",
            Element::ChangedVersion => "changed-version",
            Element::CheckedIn => "checked-in",
            Element::CheckedOut => "checked-out",
            Element::Checkin => "checkin",
            Element::CheckinActivity => "checkin-activity",
            Element::CheckinFork => "checkin-fork",
            Element::CheckinResponse => "checkin-response",
            Element::Checkout => "checkout",
            Element::CheckoutCheckin => "checkout-checkin",
            Element::CheckoutFork => "checkout-fork",
            Element::CheckoutResponse => "checkout-response",
            Element::CheckoutSet => "checkout-set",
            Element::CheckoutUnlockedCheckin => "checkout-unlocked-checkin",
            Element::Collection => "collection",
            Element::Comment => "comment",
            Element::CommonAncestor => "common-ancestor",
            Element::Comp => "comp",
            Element::CompFilter => "comp-filter",
            Element::CompareBaseline => "compare-baseline",
            Element::CompareBaselineReport => "compare-baseline-report",
            Element::ConflictPreview => "conflict-preview",
            Element::Contains => "contains",
            Element::Creationdate => "creationdate",
            Element::CreatorDisplayname => "creator-displayname",
            Element::CurrentActivitySet => "current-activity-set",
            Element::CurrentUserPrincipal => "current-user-principal",
            Element::CurrentUserPrivilegeSet => "current-user-privilege-set",
            Element::CurrentWorkspaceSet => "current-workspace-set",
            Element::Datatype => "datatype",
            Element::DefaultCalendarNeeded => "default-calendar-needed",
            Element::DeletedVersion => "deleted-version",
            Element::Deny => "deny",
            Element::DenyBeforeGrant => "deny-before-grant",
            Element::Depth => "depth",
            Element::Descending => "descending",
            Element::Description => "description",
            Element::Discouraged => "discouraged",
            Element::Displayname => "displayname",
            Element::Eq => "eq",
            Element::Error => "error",
            Element::Exclusive => "exclusive",
            Element::Expand => "expand",
            Element::ExpandProperty => "expand-property",
            Element::Filter => "filter",
            Element::First => "first",
            Element::Forbidden => "forbidden",
            Element::ForkOk => "fork-ok",
            Element::FreeBusyQuery => "free-busy-query",
            Element::From => "from",
            Element::Getcontentlanguage => "getcontentlanguage",
            Element::Getcontentlength => "getcontentlength",
            Element::Getcontenttype => "getcontenttype",
            Element::Getetag => "getetag",
            Element::Getctag => "getctag",
            Element::Getlastmodified => "getlastmodified",
            Element::Grammar => "grammar",
            Element::Grant => "grant",
            Element::GrantOnly => "grant-only",
            Element::Group => "group",
            Element::GroupMemberSet => "group-member-set",
            Element::GroupMembership => "group-membership",
            Element::Gt => "gt",
            Element::Gte => "gte",
            Element::Href => "href",
            Element::IgnorePreview => "ignore-preview",
            Element::Include => "include",
            Element::IncludeVersions => "include-versions",
            Element::Inherited => "inherited",
            Element::InheritedAclSet => "inherited-acl-set",
            Element::Invert => "invert",
            Element::IsCollection => "is-collection",
            Element::IsDefined => "is-defined",
            Element::IsNotDefined => "is-not-defined",
            Element::KeepCheckedOut => "keep-checked-out",
            Element::Label => "label",
            Element::LabelName => "label-name",
            Element::LabelNameSet => "label-name-set",
            Element::LabelResponse => "label-response",
            Element::LanguageDefined => "language-defined",
            Element::LanguageMatches => "language-matches",
            Element::Last => "last",
            Element::LatestActivityVersion => "latest-activity-version",
            Element::LatestActivityVersionReport => "latest-activity-version-report",
            Element::Like => "like",
            Element::Limit => "limit",
            Element::LimitFreebusySet => "limit-freebusy-set",
            Element::LimitRecurrenceSet => "limit-recurrence-set",
            Element::LimitedNumberOfAces => "limited-number-of-aces",
            Element::Literal => "literal",
            Element::LocateByHistory => "locate-by-history",
            Element::Location => "location",
            Element::LockTokenSubmitted => "lock-token-submitted",
            Element::Lockdiscovery => "lockdiscovery",
            Element::LockedCheckout => "locked-checkout",
            Element::Lockentry => "lockentry",
            Element::Lockinfo => "lockinfo",
            Element::Lockroot => "lockroot",
            Element::Lockscope => "lockscope",
            Element::Locktoken => "locktoken",
            Element::Locktype => "locktype",
            Element::Lt => "lt",
            Element::Lte => "lte",
            Element::ManagedAttachmentsServerUrl => "managed-attachments-server-URL",
            Element::Match => "match",
            Element::MaxAttachmentSize => "max-attachment-size",
            Element::MaxAttachmentsPerResource => "max-attachments-per-resource",
            Element::MaxAttendeesPerInstance => "max-attendees-per-instance",
            Element::MaxDateTime => "max-date-time",
            Element::MaxInstances => "max-instances",
            Element::MaxResourceSize => "max-resource-size",
            Element::Merge => "merge",
            Element::MergePreview => "merge-preview",
            Element::MergePreviewReport => "merge-preview-report",
            Element::MergeSet => "merge-set",
            Element::MinDateTime => "min-date-time",
            Element::MissingRequiredPrincipal => "missing-required-principal",
            Element::Mkactivity => "mkactivity",
            Element::MkactivityResponse => "mkactivity-response",
            Element::Mkcalendar => "mkcalendar",
            Element::MkcalendarResponse => "mkcalendar-response",
            Element::Mkcol => "mkcol",
            Element::MkcolResponse => "mkcol-response",
            Element::Mkredirectref => "mkredirectref",
            Element::MkredirectrefResponse => "mkredirectref-response",
            Element::Mkworkspace => "mkworkspace",
            Element::MkworkspaceResponse => "mkworkspace-response",
            Element::Mount => "mount",
            Element::Multistatus => "multistatus",
            Element::NeedPrivileges => "need-privileges",
            Element::New => "new",
            Element::NoAbstract => "no-abstract",
            Element::NoAceConflict => "no-ace-conflict",
            Element::NoAutoMerge => "no-auto-merge",
            Element::NoCheckout => "no-checkout",
            Element::NoConflictingLock => "no-conflicting-lock",
            Element::NoInheritedAceConflict => "no-inherited-ace-conflict",
            Element::NoInvert => "no-invert",
            Element::NoProtectedAceConflict => "no-protected-ace-conflict",
            Element::NoUidConflict => "no-uid-conflict",
            Element::Not => "not",
            Element::NotSupportedPrivilege => "not-supported-privilege",
            Element::Nresults => "nresults",
            Element::Opaque => "opaque",
            Element::Opdesc => "opdesc",
            Element::Open => "open",
            Element::OperandLiteral => "operand-literal",
            Element::OperandProperty => "operand-property",
            Element::OperandTypedLiteral => "operand-typed-literal",
            Element::Operators => "operators",
            Element::Options => "options",
            Element::OptionsResponse => "options-response",
            Element::Or => "or",
            Element::Order => "order",
            Element::OrderMember => "order-member",
            Element::Orderby => "orderby",
            Element::OrderingType => "ordering-type",
            Element::Orderpatch => "orderpatch",
            Element::OrderpatchResponse => "orderpatch-response",
            Element::Owner => "owner",
            Element::ParamFilter => "param-filter",
            Element::Parent => "parent",
            Element::ParentSet => "parent-set",
            Element::Permanent => "permanent",
            Element::Position => "position",
            Element::PredecessorSet => "predecessor-set",
            Element::Principal => "principal",
            Element::PrincipalUrl => "principal-URL",
            Element::PrincipalAddress => "principal-address",
            Element::PrincipalCollectionSet => "principal-collection-set",
            Element::PrincipalMatch => "principal-match",
            Element::PrincipalProperty => "principal-property",
            Element::PrincipalPropertySearch => "principal-property-search",
            Element::PrincipalSearchProperty => "principal-search-property",
            Element::PrincipalSearchPropertySet => "principal-search-property-set",
            Element::Privilege => "privilege",
            Element::Prop => "prop",
            Element::PropFilter => "prop-filter",
            Element::Propdesc => "propdesc",
            Element::Properties => "properties",
            Element::Property => "property",
            Element::PropertySearch => "property-search",
            Element::Propertyupdate => "propertyupdate",
            Element::Propfind => "propfind",
            Element::Propname => "propname",
            Element::Propstat => "propstat",
            Element::Protected => "protected",
            Element::QuerySchema => "query-schema",
            Element::QuerySchemaDiscovery => "query-schema-discovery",
            Element::QuotaAvailableBytes => "quota-available-bytes",
            Element::QuotaUsedBytes => "quota-used-bytes",
            Element::Read => "read",
            Element::ReadAcl => "read-acl",
            Element::ReadCurrentUserPrivilegeSet => "read-current-user-privilege-set",
            Element::ReadFreeBusy => "read-free-busy",
            Element::Rebind => "rebind",
            Element::RebindResponse => "rebind-response",
            Element::Recipient => "recipient",
            Element::RecognizedPrincipal => "recognized-principal",
            Element::RedirectLifetime => "redirect-lifetime",
            Element::Redirectref => "redirectref",
            Element::Reftarget => "reftarget",
            Element::Remove => "remove",
            Element::Report => "report",
            Element::RequestStatus => "request-status",
            Element::RequiredPrincipal => "required-principal",
            Element::Resource => "resource",
            Element::ResourceId => "resource-id",
            Element::Resourcetype => "resourcetype",
            Element::Response => "response",
            Element::Responsedescription => "responsedescription",
            Element::RootVersion => "root-version",
            Element::SameOrganizerInAllComponents => "same-organizer-in-all-components",
            Element::ScheduleCalendarTransp => "schedule-calendar-transp",
            Element::ScheduleDefaultCalendarUrl => "schedule-default-calendar-URL",
            Element::ScheduleDeliver => "schedule-deliver",
            Element::ScheduleDeliverInvite => "schedule-deliver-invite",
            Element::ScheduleDeliverReply => "schedule-deliver-reply",
            Element::ScheduleInbox => "schedule-inbox",
            Element::ScheduleInboxUrl => "schedule-inbox-URL",
            Element::ScheduleOutbox => "schedule-outbox",
            Element::ScheduleOutboxUrl => "schedule-outbox-URL",
            Element::ScheduleQueryFreebusy => "schedule-query-freebusy",
            Element::ScheduleResponse => "schedule-response",
            Element::ScheduleSend => "schedule-send",
            Element::ScheduleSendFreebusy => "schedule-send-freebusy",
            Element::ScheduleSendInvite => "schedule-send-invite",
            Element::ScheduleSendReply => "schedule-send-reply",
            Element::ScheduleTag => "schedule-tag",
            Element::Scope => "scope",
            Element::Score => "score",
            Element::Searchable => "searchable",
            Element::Segment => "segment",
            Element::Select => "select",
            Element::Selectable => "selectable",
            Element::Self_ => "self",
            Element::Set => "set",
            Element::Shared => "shared",
            Element::Sortable => "sortable",
            Element::Source => "source",
            Element::Status => "status",
            Element::SubactivitySet => "subactivity-set",
            Element::SubbaselineSet => "subbaseline-set",
            Element::SuccessorSet => "successor-set",
            Element::SupportedAddressData => "supported-address-data",
            Element::SupportedCalendarComponentSet => "supported-calendar-component-set",
            Element::SupportedCalendarData => "supported-calendar-data",
            Element::SupportedCollation => "supported-collation",
            Element::SupportedCollationSet => "supported-collation-set",
            Element::SupportedFilter => "supported-filter",
            Element::SupportedLiveProperty => "supported-live-property",
            Element::SupportedLivePropertySet => "supported-live-property-set",
            Element::SupportedMethod => "supported-method",
            Element::SupportedMethodSet => "supported-method-set",
            Element::SupportedPrivilege => "supported-privilege",
            Element::SupportedPrivilegeSet => "supported-privilege-set",
            Element::SupportedQueryGrammar => "supported-query-grammar",
            Element::SupportedQueryGrammarSet => "supported-query-grammar-set",
            Element::SupportedReport => "supported-report",
            Element::SupportedReportSet => "supported-report-set",
            Element::SupportedRscale => "supported-rscale",
            Element::SupportedRscaleSet => "supported-rscale-set",
            Element::Supportedlock => "supportedlock",
            Element::SyncCollection => "sync-collection",
            Element::SyncLevel => "sync-level",
            Element::SyncToken => "sync-token",
            Element::Target => "target",
            Element::Temporary => "temporary",
            Element::TextMatch => "text-match",
            Element::TimeRange => "time-range",
            Element::Timeout => "timeout",
            Element::Timezone => "timezone",
            Element::TimezoneId => "timezone-id",
            Element::TimezoneServiceSet => "timezone-service-set",
            Element::Transparent => "transparent",
            Element::TypedLiteral => "typed-literal",
            Element::Unauthenticated => "unauthenticated",
            Element::Unbind => "unbind",
            Element::UnbindResponse => "unbind-response",
            Element::Uncheckout => "uncheckout",
            Element::UncheckoutResponse => "uncheckout-response",
            Element::UniqueSchedulingObjectResource => "unique-scheduling-object-resource",
            Element::Unlock => "unlock",
            Element::Unreserved => "unreserved",
            Element::Update => "update",
            Element::UpdatePreview => "update-preview",
            Element::Updateredirectref => "updateredirectref",
            Element::UpdateredirectrefResponse => "updateredirectref-response",
            Element::Url => "url",
            Element::Username => "username",
            Element::ValidOrganizer => "valid-organizer",
            Element::ValidScheduleDefaultCalendarUrl => "valid-schedule-default-calendar-URL",
            Element::ValidSchedulingMessage => "valid-scheduling-message",
            Element::Version => "version",
            Element::VersionControl => "version-control",
            Element::VersionControlResponse => "version-control-response",
            Element::VersionControlledBinding => "version-controlled-binding",
            Element::VersionControlledBindingSet => "version-controlled-binding-set",
            Element::VersionControlledConfiguration => "version-controlled-configuration",
            Element::VersionHistory => "version-history",
            Element::VersionHistoryCollectionSet => "version-history-collection-set",
            Element::VersionHistorySet => "version-history-set",
            Element::VersionName => "version-name",
            Element::VersionSet => "version-set",
            Element::VersionTree => "version-tree",
            Element::Where => "where",
            Element::Workspace => "workspace",
            Element::WorkspaceCheckoutSet => "workspace-checkout-set",
            Element::WorkspaceCollectionSet => "workspace-collection-set",
            Element::Write => "write",
            Element::WriteAcl => "write-acl",
            Element::WriteContent => "write-content",
            Element::WriteProperties => "write-properties",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Attribute<T: AttributeValue> {
    Caseless(bool),
    XsiType(XsiType),
    AllowPCData(bool),
    Name(T),
    Namespace(Namespace),
    ContentType(String),
    XmlLanguage(String),
    Version(String),
    NoValue(bool),
    TestAllOf(bool),
    MatchType(MatchType),
    NegateCondition(bool),
    Collation(Collation),
    Start(T),
    End(T),
    Unknown { param: String, value: String },
}

pub trait AttributeValue {
    fn from_str(s: &str) -> Option<Self>
    where
        Self: Sized;
}

impl<T: AttributeValue> Attribute<T> {
    pub fn from_param(param: &[u8], value: Cow<'_, str>) -> Option<Attribute<T>> {
        hashify::fnc_map!(param,
            "caseless" => {
                if let Some(b) = YesNo::from_str(value.as_ref()) {
                    return Some(Attribute::Caseless(b));
                }
            },
            "xsi:type" => {
                return Some(Attribute::XsiType(XsiType::from_str(value.as_ref()).unwrap_or(XsiType::Unsupported)));
            },
            "allow-pcdata" => {
                if let Some(b) = YesNo::from_str(value.as_ref()) {
                    return Some(Attribute::AllowPCData(b));
                }
            },
            "novalue" => {
                if let Some(b) = YesNo::from_str(value.as_ref()) {
                    return Some(Attribute::NoValue(b));
                }
            },
            "negate-condition" => {
                if let Some(b) = YesNo::from_str(value.as_ref()) {
                    return Some(Attribute::NegateCondition(b));
                }
            },
            "name" => {
                if let Some(value) = T::from_str(value.as_ref()) {
                    return Some(Attribute::Name(value));
                }
            },
            "namespace" => {
                if let Some(ns) = Namespace::try_parse(value.as_bytes()) {
                    return Some(Attribute::Namespace(ns));
                }
            },
            "content-type" => {
                return Some(Attribute::ContentType(value.into_owned()));
            },
            "version" => {
                return Some(Attribute::Version(value.into_owned()));
            },
            "test" => {
                return Some(Attribute::TestAllOf(value.eq("allof")));
            },
            "match-type" => {
                if let Some(mt) = MatchType::try_parse(value.as_ref()) {
                    return Some(Attribute::MatchType(mt));
                }
            },
            "collation" => {
                if let Some(c) = Collation::try_parse(value.as_ref()) {
                    return Some(Attribute::Collation(c));
                }
            },
            "start" => {
                if let Some(value) = T::from_str(value.as_ref()) {
                    return Some(Attribute::Start(value));
                }
            },
            "end" => {
                if let Some(value) = T::from_str(value.as_ref()) {
                    return Some(Attribute::End(value));
                }
            },
            "xml:lang" => {
                return Some(Attribute::XmlLanguage(value.into_owned()));
            },
            "xmlns" => {
                return None;
            },
            _ => {
                if param.starts_with(b"xmlns:") {
                    return None;
                }
            }
        );

        Some(Attribute::Unknown {
            param: String::from_utf8_lossy(param).into_owned(),
            value: value.into_owned(),
        })
    }
}

impl AttributeValue for String {
    fn from_str(s: &str) -> Option<Self> {
        Some(s.to_string())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum Collation {
    AsciiNumeric,
    AsciiCasemap,
    Octet,
    UnicodeCasemap,
}

impl Collation {
    pub fn try_parse(s: &str) -> Option<Self> {
        hashify::tiny_map!(s.as_bytes(),
            "i;ascii-numeric" => Collation::AsciiNumeric,
            "i;ascii-casemap" => Collation::AsciiCasemap,
            "i;octet" => Collation::Octet,
            "i;unicode-casemap" => Collation::UnicodeCasemap,
        )
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Collation::AsciiNumeric => "i;ascii-numeric",
            Collation::AsciiCasemap => "i;ascii-casemap",
            Collation::Octet => "i;octet",
            Collation::UnicodeCasemap => "i;unicode-casemap",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub enum MatchType {
    Equals,
    Contains,
    StartsWith,
    EndsWith,
}

impl MatchType {
    pub fn try_parse(s: &str) -> Option<Self> {
        hashify::tiny_map!(s.as_bytes(),
            "equals" => MatchType::Equals,
            "contains" => MatchType::Contains,
            "starts-with" => MatchType::StartsWith,
            "ends-with" => MatchType::EndsWith,
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum XsiType {
    String,
    Boolean,
    Decimal,
    Float,
    Double,
    Duration,
    DateTime,
    Time,
    Date,
    GYearMonth,
    GYear,
    GMonthDay,
    GDay,
    GMonth,
    HexBinary,
    Base64Binary,
    AnyUri,
    QName,
    Notation,
    Unsupported,
}

impl XsiType {
    fn from_str(s: &str) -> Option<Self> {
        hashify::tiny_map!(s.as_bytes(),
            "xs:string" => XsiType::String,
            "xs:boolean" => XsiType::Boolean,
            "xs:decimal" => XsiType::Decimal,
            "xs:float" => XsiType::Float,
            "xs:double" => XsiType::Double,
            "xs:duration" => XsiType::Duration,
            "xs:dateTime" => XsiType::DateTime,
            "xs:time" => XsiType::Time,
            "xs:date" => XsiType::Date,
            "xs:gYearMonth" => XsiType::GYearMonth,
            "xs:gYear" => XsiType::GYear,
            "xs:gMonthDay" => XsiType::GMonthDay,
            "xs:gDay" => XsiType::GDay,
            "xs:gMonth" => XsiType::GMonth,
            "xs:hexBinary" => XsiType::HexBinary,
            "xs:base64Binary" => XsiType::Base64Binary,
            "xs:anyURI" => XsiType::AnyUri,
            "xs:QName" => XsiType::QName,
            "xs:NOTATION" => XsiType::Notation,
        )
    }
}

struct YesNo;

impl YesNo {
    fn from_str(s: &str) -> Option<bool> {
        hashify::tiny_map!(s.as_bytes(),
            "yes" => true,
            "no" => false,
        )
    }
}

impl TextMatch {
    pub fn matches(&self, text: &str) -> bool {
        match self.collation {
            Collation::Octet => {
                (match self.match_type {
                    MatchType::Equals => text == self.value,
                    MatchType::Contains => text.contains(&self.value),
                    MatchType::StartsWith => text.starts_with(&self.value),
                    MatchType::EndsWith => text.ends_with(&self.value),
                }) ^ self.negate
            }
            _ => {
                (match self.match_type {
                    MatchType::Equals => text.to_lowercase() == self.value.to_lowercase(),
                    MatchType::Contains => text.to_lowercase().contains(&self.value.to_lowercase()),
                    MatchType::StartsWith => {
                        text.to_lowercase().starts_with(&self.value.to_lowercase())
                    }
                    MatchType::EndsWith => {
                        text.to_lowercase().ends_with(&self.value.to_lowercase())
                    }
                }) ^ self.negate
            }
        }
    }
}
