------------------------------------------------------------------------------
--                     XML/Ada - An XML suite for Ada95                     --
--                                                                          --
--                     Copyright (C) 2010-2017, AdaCore                     --
--                                                                          --
-- This library is free software;  you can redistribute it and/or modify it --
-- under terms of the  GNU General Public License  as published by the Free --
-- Software  Foundation;  either version 3,  or (at your  option) any later --
-- version. This library is distributed in the hope that it will be useful, --
-- but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN- --
-- TABILITY or FITNESS FOR A PARTICULAR PURPOSE.                            --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
------------------------------------------------------------------------------

pragma Ada_05;

with Ada.Unchecked_Deallocation;
with Ada.Exceptions;            use Ada.Exceptions;
with Ada.Strings.Fixed;         use Ada.Strings.Fixed;
with Ada.Strings.Unbounded;     use Ada.Strings.Unbounded;
with Sax.Encodings;             use Sax.Encodings;
with Unicode;                   use Unicode;
with Unicode.Names.Basic_Latin; use Unicode.Names.Basic_Latin;

package body Schema.Simple_Types is

   use Simple_Type_Tables, Enumeration_Tables;

   procedure Unchecked_Free is new Ada.Unchecked_Deallocation
     (Pattern_Matcher_Array, Pattern_Matcher_Array_Access);
   procedure Unchecked_Free is new Ada.Unchecked_Deallocation
     (Pattern_Matcher, Pattern_Matcher_Access);

   generic
      with function Get_Length (Ch : Byte_Sequence) return Natural;
   function Validate_Length_Facets
     (Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      Mask          : Facets_Mask;
      Length, Min_Length, Max_Length : Integer) return Symbol;
   --  Validate length facets

   generic
      type T is private;
      with procedure Value (Symbols : Symbol_Table;
                            Ch      : Byte_Sequence;
                            Val     : out T;
                            Error   : out Symbol) is <>;
      with function Image (Val : T) return Byte_Sequence is <>;
      with function "<" (T1, T2 : T) return Boolean is <>;
      with function "<=" (T1, T2 : T) return Boolean is <>;
   procedure Validate_Range
     (Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      Mask          : Facets_Mask;
      Min_Inclusive : T;
      Min_Exclusive : T;
      Max_Inclusive : T;
      Max_Exclusive : T;
      Error         : out Symbol;
      Val           : out T);

   generic
      type T is private;
      with procedure Value (Symbols : Symbol_Table;
                            Ch      : Byte_Sequence;
                            Val     : out T;
                            Error   : out Symbol) is <>;
   procedure Override_Single_Range_Facet
     (Symbols       : Sax.Utils.Symbol_Table;
      Facets        : All_Facets;
      Facet         : Facet_Enum;
      Mask          : in out Facets_Mask;
      Val           : in out T;
      Error         : in out Symbol;
      Error_Loc     : in out Location);

   generic
      type T is private;
      with procedure Value (Symbols : Symbol_Table;
                            Ch      : Byte_Sequence;
                            Val     : out T;
                            Error   : out Symbol) is <>;
   procedure Override_Range_Facets
     (Symbols       : Sax.Utils.Symbol_Table;
      Facets        : All_Facets;
      Mask          : in out Facets_Mask;
      Min_Inclusive : in out T;
      Min_Exclusive : in out T;
      Max_Inclusive : in out T;
      Max_Exclusive : in out T;
      Error         : out Symbol;
      Error_Loc     : out Location);
   --  Override some range facets

   procedure Override_Length_Facets
     (Symbols       : Sax.Utils.Symbol_Table;
      Facets        : All_Facets;
      Mask          : in out Facets_Mask;
      Length        : in out Integer;
      Min_Length    : in out Integer;
      Max_Length    : in out Integer;
      Error         : out Symbol;
      Error_Loc     : out Location);
   --  Override some length facets

   generic
      type T is private;
      with procedure Value (Symbols : Symbol_Table;
                            Ch      : Byte_Sequence;
                            Val     : out T;
                            Error   : out Symbol) is <>;
      with function "=" (T1, T2 : T) return Boolean is <>;
      with function Image (T1 : T) return String is <>;
   function Generic_Equal
     (Symbols : Symbol_Table;
      Val1    : Symbol;
      Val2    : Byte_Sequence) return Boolean;
   --  Compare two values, after possibly normalizing them given the type
   --  definition (whitespaces, remove left-most 0 when appropriate,...).
   --  This assumes [Val1] and [Val2] are valid values for the type (ie they
   --  have already been validated).

   function Validate_List_Facets
     (Descr   : Simple_Type_Descr;
      Symbols : Sax.Utils.Symbol_Table;
      Ch      : Unicode.CES.Byte_Sequence;
      Length, Min_Length, Max_Length : Integer) return Symbol;
   --  Validate the facets for a list

   procedure Check_Id
     (Symbols   : Symbol_Table;
      Id_Table  : in out Symbol_Htable_Access;
      Value     : Unicode.CES.Byte_Sequence;
      Error     : in out Symbol);
   --  Check whether Value is a unique ID in the document.
   --  If yes, store it in Id_Table to ensure its future uniqueness.
   --  Return the error message or [No_Symbol]

   -------------------
   -- Generic_Equal --
   -------------------

   function Generic_Equal
     (Symbols : Symbol_Table;
      Val1    : Symbol;
      Val2    : Byte_Sequence) return Boolean
   is
      V1, V2 : T;
      Error  : Symbol;
   begin
      Value (Symbols, Get (Val1).all, V1, Error);
      if Error /= No_Symbol then
         if Debug then
            Debug_Output ("Generic_Equal, could not convert Val1 "
                          & Get (Val1).all & " => " & Get (Error).all);
         end if;
         return False;
      end if;

      Value (Symbols, Val2, V2, Error);
      if Error /= No_Symbol then
         if Debug then
            Debug_Output ("Generic_Equal, could not convert Val2 "
                          & Val2 & " => " & Get (Error).all);
         end if;
         return False;
      end if;

      if Debug then
         Debug_Output ("Comparing " & Image (V1) & " != " & Image (V2));
      end if;

      return V1 = V2;
   end Generic_Equal;

   ----------------------------
   -- Validate_Length_Facets --
   ----------------------------

   function Validate_Length_Facets
     (Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      Mask          : Facets_Mask;
      Length, Min_Length, Max_Length : Integer) return Symbol
   is
      L : Integer := -1;
   begin
      --  Characters are always a string, nothing to check here but the facets

      if Mask (Facet_Length)
        or else Mask (Facet_Min_Length)
        or else Mask (Facet_Max_Length)
      then
         L := Get_Length (Ch);
      else
         return No_Symbol;
      end if;

      if Mask (Facet_Length) then
         if L /= Length then
            return Find
              (Symbols,
               "Invalid length, must be"
               & Integer'Image (Length) & " characters");
         end if;
      end if;

      if Mask (Facet_Min_Length) then
         if L < Min_Length then
            return Find
              (Symbols,
               "String is too short, minimum length is"
               & Integer'Image (Min_Length)
               & " characters");
         end if;
      end if;

      if Mask (Facet_Max_Length) then
         if L > Max_Length then
            return Find
              (Symbols,
               "String is too long, maximum length is"
               & Integer'Image (Max_Length)
               & " characters");
         end if;
      end if;

      return No_Symbol;
   end Validate_Length_Facets;

   ---------------------
   --  Instantiations --
   ---------------------

   function HexBinary_Get_Length
     (Value : Unicode.CES.Byte_Sequence) return Natural;
   function Base64Binary_Get_Length
     (Value : Unicode.CES.Byte_Sequence) return Natural;
   --  Return the length of a string

   procedure Value (Symbols : Symbol_Table;
                    Ch      : Byte_Sequence;
                    Val     : out XML_Float;
                    Error   : out Symbol);
   --  Converts [Ch] into [Val]

   function Validate_String
     (Descr   : Simple_Type_Descr;
      Symbols : Sax.Utils.Symbol_Table;
      Ch      : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_HexBinary_Facets is new
     Validate_Length_Facets (HexBinary_Get_Length);
   function Validate_Base64Binary_Facets is new
     Validate_Length_Facets (Base64Binary_Get_Length);

   function Validate_NMTOKEN
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol;
   function Validate_NMTOKENS
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol;
   function Validate_Name
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol;
   function Validate_NCName
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol;
   function Validate_NCNames
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol;
   function Validate_Language
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_URI
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_HexBinary
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Notation
     (Notations     : Symbol_Htable.HTable;
      Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Base64Binary
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_QName
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol;
   function Validate_Boolean
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Double
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Decimal
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Duration
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Date_Time
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Date
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_Time
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_GDay
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_GMonth_Day
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_GMonth
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_GYear
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   function Validate_GYear_Month
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol;
   --  Check [Ch] for one of the primitive types, including facets

   function Anchor (Str : String) return String;
   --  Return an anchored version of Str ("^...$").
   --  In XML, regexps are always anchored, as per the beginning of [G]

   function Missing_End_Anchor (Str : String) return Boolean;
   function Missing_Start_Anchor (Str : String) return Boolean;
   --  Whether the regexp is missing the "^" or "$" anchors

   procedure Boolean_Value
     (Symbols : Symbol_Table;
      Ch      : Byte_Sequence;
      Val     : out Boolean;
      Error   : out Symbol);
   --  Converts [Ch] to a boolean, and returns an error message if needed

   function Equal_Boolean     is new Generic_Equal (Boolean, Boolean_Value,
                                                    Image => Boolean'Image);
   function Equal_Float       is new Generic_Equal (XML_Float, Value);
   function Equal_Decimal    is new Generic_Equal (Arbitrary_Precision_Number);
   function Equal_Duration    is new Generic_Equal (Duration_T);
   function Equal_Date_Time   is new Generic_Equal (Date_Time_T);
   function Equal_Date        is new Generic_Equal (Date_T);
   function Equal_Time        is new Generic_Equal (Time_T);
   function Equal_GDay        is new Generic_Equal (GDay_T);
   function Equal_GMonth_Day  is new Generic_Equal (GMonth_Day_T);
   function Equal_GMonth      is new Generic_Equal (GMonth_T);
   function Equal_GYear       is new Generic_Equal (GYear_T);
   function Equal_GYear_Month is new Generic_Equal (GYear_Month_T);

   -------------------------------
   -- Register_Predefined_Types --
   -------------------------------

   procedure Register_Predefined_Types (Symbols : Sax.Utils.Symbol_Table) is
      Zero : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "0"));
      One  : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "1"));
      Minus_One  : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "-1"));
      Max_Unsigned_Long : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+18446744073709551615"));
      Max_Long : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+9223372036854775807"));
      Min_Long : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "-9223372036854775808"));
      Max_Int : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+2147483647"));
      Min_Int : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "-2147483648"));
      Max_Short : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+32767"));
      Min_Short : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "-32768"));
      Max_Byte : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+127"));
      Min_Byte : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "-128"));
      Max_Unsigned_Int : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+4294967295"));
      Max_Unsigned_Short : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+65535"));
      Max_Unsigned_Byte : constant Arbitrary_Precision_Number :=
        Value (Find (Symbols, "+255"));

      Whitespace_Mask : constant Facets_Mask := (Facet_Whitespace => True,
                                                 others           => False);
      Fraction_Min_Mask : constant Facets_Mask :=
        (Facet_Fraction_Digits => True,
         Facet_Min_Inclusive   => True,
         others                => False);
      Fraction_Max_Mask : constant Facets_Mask :=
        (Facet_Fraction_Digits => True,
         Facet_Max_Inclusive   => True,
         others                => False);
      Fraction_Min_Max_Mask : constant Facets_Mask :=
        (Facet_Fraction_Digits => True,
         Facet_Min_Inclusive   => True,
         Facet_Max_Inclusive   => True,
         others                => False);

      Any_Simple_Type, Decimal, Integer, Long, Int, Short : Type_Index;
      Non_Positive_Int, Non_Negative_Int : Type_Index;
      Unsigned_Long, Unsigned_Int, Unsigned_Short : Type_Index;
      Str, Normalized_Str, Token : Type_Index;
      Name, NCName : Type_Index;
      T : Type_Index;
      pragma Unreferenced (T);
   begin
      Any_Simple_Type := Register
        ("anySimpleType",
         (Kind       => Primitive_String,
          Mask       => (Facet_Whitespace => True,
                         others           => False),
          Whitespace => Preserve,
          others     => <>), No_Type_Index);

      Str := Register ("string", (Kind       => Primitive_String,
                                  Mask       => Whitespace_Mask,
                                  Whitespace => Preserve,
                                  others     => <>), Any_Simple_Type);
      Normalized_Str :=
        Register ("normalizedString", (Kind       => Primitive_String,
                                       Mask       => Whitespace_Mask,
                                       Whitespace => Replace,
                                       others => <>), Str);
      Token := Register ("token", (Kind       => Primitive_String,
                                   Mask       => Whitespace_Mask,
                                   Whitespace => Collapse,
                                   others => <>), Normalized_Str);
      T := Register ("language", (Kind       => Primitive_Language,
                             Mask       => Whitespace_Mask,
                             Whitespace => Collapse,
                             others => <>), Token);
      T := Register ("boolean",  (Kind => Primitive_Boolean, others => <>),
                     Any_Simple_Type);
      T := Register ("QName", (Kind => Primitive_QName,
                               Mask => Whitespace_Mask,
                               others => <>),
                     Any_Simple_Type);
      T := Register ("NOTATION", (Kind => Primitive_Notation, others => <>),
                     Any_Simple_Type);
      T := Register ("float",    (Kind => Primitive_Float, others => <>),
                     Any_Simple_Type);
      T := Register ("NMTOKEN",  (Kind => Primitive_NMTOKEN, others => <>),
                     Token);
      T := Register ("NMTOKENS", (Kind => Primitive_NMTOKENS,
                                  Mask => Whitespace_Mask,
                                  others => <>),
        Any_Simple_Type);
      Name := Register ("Name",     (Kind       => Primitive_Name,
                                     Mask       => Whitespace_Mask,
                                     Whitespace => Collapse,
                                     others => <>), Token);
      NCName := Register ("NCName",   (Kind       => Primitive_NCName,
                                       Mask       => Whitespace_Mask,
                                       Whitespace => Collapse,
                                       others => <>), Name);
      T := Register ("ID",       (Kind       => Primitive_ID,
                                  Mask       => Whitespace_Mask,
                                  Whitespace => Preserve,
                                  others => <>), NCName);
      T := Register ("IDREF",    (Kind       => Primitive_NCName,
                                  Mask       => Whitespace_Mask,
                                  Whitespace => Preserve,
                                  others => <>), NCName);
      T := Register ("IDREFS",   (Kind       => Primitive_NCNames,
                                  Mask       => Whitespace_Mask,
                                  Whitespace => Preserve,
                                  others => <>), Any_Simple_Type);
      T := Register ("ENTITY",   (Kind       => Primitive_NCName,
                                  Mask       => Whitespace_Mask,
                                  Whitespace => Preserve,
                                  others => <>), NCName);
      T := Register ("ENTITIES", (Kind       => Primitive_NCNames,
                                  Mask       => Whitespace_Mask,
                                  Whitespace => Preserve,
                                  others => <>), Any_Simple_Type);
      T := Register ("anyURI",   (Kind => Primitive_Any_URI,
                                  Mask => Whitespace_Mask,
                                  others => <>), Any_Simple_Type);
      T := Register ("hexBinary", (Kind => Primitive_HexBinary,
                                   others => <>), Any_Simple_Type);
      T := Register ("base64Binary", (Kind => Primitive_Base64Binary,
                                      others => <>), Any_Simple_Type);
      Decimal := Register ("decimal",
                           (Kind => Primitive_Decimal, others => <>),
                           Any_Simple_Type);
      Integer := Register ("integer",
                           (Kind                  => Primitive_Decimal,
                            Fraction_Digits       => 0,
                            Whitespace            => Collapse,
                            Pattern               => new Pattern_Matcher_Array'
                               (1 => Pattern_Facet'(
                                   Str => Find (Symbols, "[\-+]?[0-9]+"),
                                   Pattern => new Pattern_Matcher'
                                       (Compile ("[\-+]?[0-9]+")))),
                            Mask => (Facet_Fraction_Digits => True,
                                     Facet_Pattern         => True,
                                     Facet_Whitespace      => True,
                                     others                => False),
                            others => <>), Decimal);
      Non_Negative_Int :=
        Register ("nonNegativeInteger",
                  (Kind                  => Primitive_Decimal,
                   Fraction_Digits       => 0,
                   Decimal_Min_Inclusive => Zero,
                   Mask                  => Fraction_Min_Mask,
                   others                => <>), Integer);
      Unsigned_Long :=
        Register ("unsignedLong",
                  (Kind                 => Primitive_Decimal,
                   Mask                 => Fraction_Min_Max_Mask,
                   Fraction_Digits       => 0,
                   Decimal_Min_Inclusive => Zero,
                   Decimal_Max_Inclusive => Max_Unsigned_Long,
                   others                => <>), Non_Negative_Int);
      T := Register ("positiveInteger",
                     (Kind                  => Primitive_Decimal,
                      Fraction_Digits       => 0,
                      Decimal_Min_Inclusive => One,
                      Mask                  => Fraction_Min_Mask,
                      others                => <>), Non_Negative_Int);
      Non_Positive_Int :=
        Register ("nonPositiveInteger",
                  (Kind                  => Primitive_Decimal,
                   Fraction_Digits       => 0,
                   Decimal_Max_Inclusive => Zero,
                   Mask                  => Fraction_Max_Mask,
                   others                => <>), Integer);
      T := Register ("negativeInteger",
                     (Kind                  => Primitive_Decimal,
                      Fraction_Digits       => 0,
                      Decimal_Max_Inclusive => Minus_One,
                      Mask                  => Fraction_Max_Mask,
                      others                => <>), Non_Positive_Int);
      Long := Register ("long",
                        (Kind               => Primitive_Decimal,
                         Mask               => Fraction_Min_Max_Mask,
                         Fraction_Digits       => 0,
                         Decimal_Max_Inclusive => Max_Long,
                         Decimal_Min_Inclusive => Min_Long,
                         others                => <>), Integer);
      Int := Register ("int",
                       (Kind                  => Primitive_Decimal,
                        Mask                  => Fraction_Min_Max_Mask,
                        Fraction_Digits       => 0,
                        Decimal_Max_Inclusive => Max_Int,
                        Decimal_Min_Inclusive => Min_Int,
                        others                => <>), Long);
      Short := Register ("short",
                         (Kind                  => Primitive_Decimal,
                          Mask                  => Fraction_Min_Max_Mask,
                          Fraction_Digits       => 0,
                          Decimal_Max_Inclusive => Max_Short,
                          Decimal_Min_Inclusive => Min_Short,
                          others                => <>), Int);
      T := Register ("byte",
                     (Kind                  => Primitive_Decimal,
                      Mask                  => Fraction_Min_Max_Mask,
                      Fraction_Digits       => 0,
                      Decimal_Max_Inclusive => Max_Byte,
                      Decimal_Min_Inclusive => Min_Byte,
                      others                => <>), Short);
      Unsigned_Int :=
        Register ("unsignedInt",
                  (Kind                  => Primitive_Decimal,
                   Mask                  => Fraction_Min_Max_Mask,
                   Fraction_Digits       => 0,
                   Decimal_Max_Inclusive => Max_Unsigned_Int,
                   Decimal_Min_Inclusive => Zero,
                   others                => <>), Unsigned_Long);
      Unsigned_Short :=
        Register ("unsignedShort",
                  (Kind                  => Primitive_Decimal,
                   Mask                  => Fraction_Min_Max_Mask,
                   Fraction_Digits       => 0,
                   Decimal_Max_Inclusive => Max_Unsigned_Short,
                   Decimal_Min_Inclusive => Zero,
                   others                => <>), Unsigned_Int);
      T :=
        Register ("unsignedByte",
                  (Kind                  => Primitive_Decimal,
                   Mask                  => Fraction_Min_Max_Mask,
                   Fraction_Digits       => 0,
                   Decimal_Max_Inclusive => Max_Unsigned_Byte,
                   Decimal_Min_Inclusive => Zero,
                   others                => <>), Unsigned_Short);
      T := Register ("float", (Kind => Primitive_Float, others => <>),
                     Any_Simple_Type);
      T := Register ("double", (Kind => Primitive_Double, others => <>),
                     Any_Simple_Type);
      T := Register ("time", (Kind => Primitive_Time, others => <>),
                     Any_Simple_Type);
      T := Register ("dateTime", (Kind => Primitive_DateTime, others => <>),
                     Any_Simple_Type);
      T := Register ("gDay",       (Kind => Primitive_GDay, others => <>),
                     Any_Simple_Type);
      T := Register ("gMonthDay",  (Kind => Primitive_GMonthDay, others => <>),
                     Any_Simple_Type);
      T := Register ("gMonth",     (Kind => Primitive_GMonth, others => <>),
                     Any_Simple_Type);
      T := Register ("gYearMonth",
                     (Kind => Primitive_GYearMonth, others => <>),
                     Any_Simple_Type);
      T := Register ("gYear", (Kind => Primitive_GYear, others => <>),
                     Any_Simple_Type);
      T := Register ("date",       (Kind => Primitive_Date, others => <>),
                     Any_Simple_Type);
      T := Register ("duration",   (Kind => Primitive_Duration, others => <>),
                     Any_Simple_Type);

      --  Missing attribute "xml:lang" of type "language"
   end Register_Predefined_Types;

   -----------
   -- Equal --
   -----------

   procedure Equal
     (Simple_Types  : Simple_Type_Table;
      Enumerations  : Enumeration_Tables.Instance;
      Notations     : Symbol_Htable.HTable;
      Symbols       : Symbol_Table;
      Id_Table      : in out Symbol_Htable_Access;
      Simple_Type   : Simple_Type_Index;
      Ch1           : Sax.Symbols.Symbol;
      Ch2           : Unicode.CES.Byte_Sequence;
      Is_Equal      : out Boolean;
      XML_Version   : XML_Versions)
   is
      Descr : Simple_Type_Descr renames Simple_Types.Table (Simple_Type);
      Error : Symbol;
   begin
      case Descr.Kind is
         when Primitive_String .. Primitive_HexBinary =>
            Is_Equal := Get (Ch1).all = Ch2;
         when Primitive_Boolean   =>
            Is_Equal := Equal_Boolean (Symbols, Ch1, Ch2);
         when Primitive_Float | Primitive_Double  =>
            Is_Equal := Equal_Float (Symbols, Ch1, Ch2);
         when Primitive_Decimal   =>
            Is_Equal := Equal_Decimal (Symbols, Ch1, Ch2);
         when Primitive_Time      =>
            Is_Equal := Equal_Time (Symbols, Ch1, Ch2);
         when Primitive_DateTime =>
            Is_Equal := Equal_Date_Time (Symbols, Ch1, Ch2);
         when Primitive_GDay      =>
            Is_Equal := Equal_GDay (Symbols, Ch1, Ch2);
         when Primitive_GMonth    =>
            Is_Equal := Equal_GMonth (Symbols, Ch1, Ch2);
         when Primitive_GYear     =>
            Is_Equal := Equal_GYear (Symbols, Ch1, Ch2);
         when Primitive_Date      =>
            Is_Equal := Equal_Date (Symbols, Ch1, Ch2);
         when Primitive_Duration  =>
            Is_Equal := Equal_Duration (Symbols, Ch1, Ch2);
         when Primitive_GMonthDay =>
            Is_Equal := Equal_GMonth_Day (Symbols, Ch1, Ch2);
         when Primitive_GYearMonth =>
            Is_Equal := Equal_GYear_Month (Symbols, Ch1, Ch2);

         when Primitive_Union =>
            for S in Descr.Union'Range loop
               if Descr.Union (S) /= No_Simple_Type_Index then
                  --  We need to do space normalization here: when there is
                  --  a single type (ie not a union), the normalization has
                  --  already been done for the "fixed" value, but this isn't
                  --  doable in the case of union where the normalization
                  --  depends on which member is selected.
                  --  We actually also need to validate the value since we
                  --  don't know precisely for which members it is valid.

                  declare
                     Norm : Byte_Sequence := Get (Ch1).all;
                     Last : Integer := Norm'Last;
                  begin
                     Normalize_Whitespace
                       (Whitespace =>
                          Simple_Types.Table (Descr.Union (S)).Whitespace,
                        Val        => Norm,
                        Last       => Last);

                     if Debug then
                        Debug_Output
                          ("Equal for union, checking simpleType:"
                           & Descr.Union (S)'Img & " "
                           & Simple_Types.Table
                             (Descr.Union (S)).Whitespace'Img
                           & " Ch1=["
                           & Norm (Norm'First .. Last) & "]");
                     end if;

                     Validate_Simple_Type
                       (Simple_Types  => Simple_Types,
                        Enumerations  => Enumerations,
                        Notations     => Notations,
                        Symbols       => Symbols,
                        Id_Table      => Id_Table,
                        Insert_Id     => False,
                        Simple_Type   => Descr.Union (S),
                        Ch            => Norm (Norm'First .. Last),
                        Error         => Error,
                        XML_Version   => XML_Version);

                     if Debug and then Error /= No_Symbol then
                        Debug_Output
                          ("Equal: member doesn't apply: "
                           & Get (Error).all);
                     end if;

                     if Error = No_Symbol then
                        Equal
                          (Simple_Types => Simple_Types,
                           Enumerations => Enumerations,
                           Notations    => Notations,
                           Id_Table     => Id_Table,
                           Symbols      => Symbols,
                           Simple_Type  => Descr.Union (S),
                           Ch1  => Find (Symbols, Norm (Norm'First .. Last)),
                           Ch2  => Ch2,
                           Is_Equal     => Is_Equal,
                           XML_Version  => XML_Version);
                        if Is_Equal then
                           return;
                        end if;
                     end if;
                  end;
               end if;
            end loop;

            Is_Equal := False;

         when Primitive_List =>
            Is_Equal := Get (Ch1).all = Ch2;
      end case;
   end Equal;

   -------------------------------------
   -- Validate_Simple_Type_Characters --
   -------------------------------------

   procedure Validate_Simple_Type
     (Simple_Types  : Simple_Type_Table;
      Enumerations  : Enumeration_Tables.Instance;
      Notations     : Symbol_Htable.HTable;
      Symbols       : Symbol_Table;
      Id_Table      : in out Symbol_Htable_Access;
      Insert_Id     : Boolean := True;
      Simple_Type   : Simple_Type_Index;
      Ch            : Unicode.CES.Byte_Sequence;
      Error         : in out Symbol;
      XML_Version   : XML_Versions)
   is
      Descr : Simple_Type_Descr renames Simple_Types.Table (Simple_Type);
      Index : Integer;
      Char  : Unicode_Char;
      Matched : Match_Array (0 .. 0);

      procedure Validate_List_Item (Str : Byte_Sequence);
      procedure Validate_List_Item (Str : Byte_Sequence) is
      begin
         if Error = No_Symbol then
            Validate_Simple_Type
              (Simple_Types, Enumerations, Notations, Symbols, Id_Table,
               Simple_Type   => Descr.List_Item,
               Ch            => Str,
               Error         => Error,
               XML_Version   => XML_Version);
         end if;
      end Validate_List_Item;

      procedure Validate_List_Items is new For_Each_Item (Validate_List_Item);

   begin
      Error := No_Symbol;

      --  Check common facets

      if Descr.Mask (Facet_Enumeration) then
         declare
            Enum  : Enumeration_Index := Descr.Enumeration;
            Found : Boolean := False;
         begin
            while Enum /= No_Enumeration_Index loop
               Equal
                 (Simple_Types, Enumerations, Notations, Symbols, Id_Table,
                  Simple_Type,
                  Ch1 => Enumerations.Table (Enum).Value,
                  Ch2 => Ch,
                  Is_Equal => Found,
                  XML_Version => XML_Version);
               exit when Found;

               Enum := Enumerations.Table (Enum).Next;
            end loop;

            if not Found then
               Error := Find (Symbols, "Value not in the enumeration set");
               return;
            end if;
         end;
      end if;

      if Descr.Mask (Facet_Whitespace) then
         case Descr.Whitespace is
         when Preserve =>
            null; --  Always valid

         when Replace =>
            for C in Ch'Range loop
               if Ch (C) = ASCII.HT
                 or else Ch (C) = ASCII.LF
                 or else Ch (C) = ASCII.CR
               then
                  Error :=
                    Find (Symbols, "HT, LF and CR characters not allowed");
                  return;
               end if;
            end loop;

         when Collapse =>
            for C in Ch'Range loop
               if Ch (C) = ASCII.HT
                 or else Ch (C) = ASCII.LF
                 or else Ch (C) = ASCII.CR
               then
                  Error :=
                    Find (Symbols, "HT, LF and CR characters not allowed");
                  return;

               elsif Ch (C) = ' '
                 and then C < Ch'Last
                 and then Ch (C + 1) = ' '
               then
                  Error := Find
                    (Symbols, "Duplicate space characters not allowed");
                  return;
               end if;
            end loop;

            --  Leading or trailing white spaces are also forbidden
            if Ch'Length /= 0 then
               if Ch (Ch'First) = ' ' then
                  Error := Find
                    (Symbols, "Leading whitespaces not allowed");
                  return;
               elsif Ch (Ch'Last) = ' ' then
                  Error := Find
                    (Symbols, "Trailing whitespaces not allowed");
                  return;
               end if;
            end if;
         end case;
      end if;

      if Descr.Mask (Facet_Pattern) and then Descr.Pattern /= null then
         --  Check whether we have unicode char outside of ASCII

         Index := Ch'First;
         while Index <= Ch'Last loop
            Encoding.Read (Ch, Index, Char);
            if Char > 127 then
               --  Start with '#' because this is a non-implemented feature
               Error := Find
                 (Symbols, "#Regexp matching with unicode not supported");
               return;
            end if;
         end loop;

         for P in Descr.Pattern'Range loop
            Match (Descr.Pattern (P).Pattern.all, String (Ch), Matched);
            if Matched (0).First /= Ch'First
              or else Matched (0).Last /= Ch'Last
            then
               Error := Find
                 (Symbols,
                  "string pattern not matched: "
                  & Get (Descr.Pattern (P).Str).all);
               return;
            end if;
         end loop;
      end if;

      --  Type-specific facets

      case Descr.Kind is
         when Primitive_String =>
            Error :=  Validate_String (Descr, Symbols, Ch);
         when Primitive_HexBinary =>
            Error := Validate_HexBinary (Descr, Symbols, Ch);
         when Primitive_Notation =>
            Error := Validate_Notation (Notations, Descr, Symbols, Ch);
         when Primitive_Base64Binary =>
            Error := Validate_Base64Binary (Descr, Symbols, Ch);
         when Primitive_Language =>
            Error := Validate_Language (Descr, Symbols, Ch);
         when Primitive_QName    =>
            Error := Validate_QName (Descr, Symbols, Ch, XML_Version);
         when Primitive_NCName   =>
            Error := Validate_NCName (Descr, Symbols, Ch, XML_Version);
         when Primitive_ID       =>
            Error := Validate_NCName (Descr, Symbols, Ch, XML_Version);
            if Error = No_Symbol and then Insert_Id then
               Check_Id (Symbols, Id_Table, Ch, Error);
            end if;
         when Primitive_NCNames  =>
            Error := Validate_NCNames (Descr, Symbols, Ch, XML_Version);
         when Primitive_Name     =>
            Error :=  Validate_Name (Descr, Symbols, Ch, XML_Version);
         when Primitive_Any_URI  =>
            Error := Validate_URI (Descr, Symbols, Ch);
         when Primitive_NMTOKEN  =>
            Error := Validate_NMTOKEN (Descr, Symbols, Ch, XML_Version);
         when Primitive_NMTOKENS =>
            Error := Validate_NMTOKENS (Descr, Symbols, Ch, XML_Version);
         when Primitive_Boolean  =>
            Error := Validate_Boolean (Descr, Symbols, Ch);
         when Primitive_Decimal  =>
            Error := Validate_Decimal (Descr, Symbols, Ch);
         when Primitive_Float | Primitive_Double  =>
            Error := Validate_Double (Descr, Symbols, Ch);
         when Primitive_Time     =>
            Error :=  Validate_Time (Descr, Symbols, Ch);
         when Primitive_DateTime =>
            Error := Validate_Date_Time (Descr, Symbols, Ch);
         when Primitive_GDay =>
            Error := Validate_GDay (Descr, Symbols, Ch);
         when Primitive_GMonthDay =>
            Error := Validate_GMonth_Day (Descr, Symbols, Ch);
         when Primitive_GMonth   =>
            Error := Validate_GMonth (Descr, Symbols, Ch);
         when Primitive_GYearMonth =>
            Error := Validate_GYear_Month (Descr, Symbols, Ch);
         when Primitive_GYear    =>
            Error := Validate_GYear (Descr, Symbols, Ch);
         when Primitive_Date     =>
            Error := Validate_Date (Descr, Symbols, Ch);
         when Primitive_Duration =>
            Error := Validate_Duration (Descr, Symbols, Ch);

         when Primitive_Union    =>
            for S in Descr.Union'Range loop
               if Descr.Union (S) /= No_Simple_Type_Index then
                  Validate_Simple_Type
                    (Simple_Types  => Simple_Types,
                     Enumerations  => Enumerations,
                     Symbols       => Symbols,
                     Notations     => Notations,
                     Id_Table      => Id_Table,
                     Simple_Type   => Descr.Union (S),
                     Ch            => Ch,
                     Error         => Error,
                     XML_Version   => XML_Version);
                  if Error = No_Symbol then
                     return;
                  else
                     if Debug then
                        Debug_Output ("Checking union at index" & S'Img
                                      & " => " & Get (Error).all);
                     end if;
                  end if;
               end if;
            end loop;

            Error := Find (Symbols, "No matching type in the union");

         when Primitive_List     =>
            Validate_List_Items (Ch);
            if Error = No_Symbol then
               Error := Validate_List_Facets
                 (Descr, Symbols, Ch,
                  Descr.List_Length, Descr.List_Min_Length,
                  Descr.List_Max_Length);
            end if;
      end case;
   end Validate_Simple_Type;

   --------------------------
   -- HexBinary_Get_Length --
   --------------------------

   function HexBinary_Get_Length
     (Value : Unicode.CES.Byte_Sequence) return Natural is
   begin
      return Sax.Encodings.Encoding.Length (Value) / 2;
   end HexBinary_Get_Length;

   -----------------------------
   -- Base64Binary_Get_Length --
   -----------------------------

   function Base64Binary_Get_Length
     (Value : Unicode.CES.Byte_Sequence) return Natural
   is
      Length : Natural := 0;
      C : Unicode_Char;
      Index : Positive := Value'First;
   begin
      while Index <= Value'Last loop
         Sax.Encodings.Encoding.Read (Value, Index, C);
         if C /= 16#20#
           and then C /= 16#A#
           and then C /= Character'Pos ('=')
         then
            Length := Length + 1;
         end if;
      end loop;
      return Length * 3 / 4;
   end Base64Binary_Get_Length;

   ----------------------
   -- Validate_NMTOKEN --
   ----------------------

   function Validate_NMTOKEN
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol is
   begin
      if not Is_Valid_Nmtoken (Ch, XML_Version) then
         return Find (Symbols, "Invalid NMTOKEN: """ & Ch & """");
      end if;
      return Validate_String (Descr, Symbols, Ch);
   end Validate_NMTOKEN;

   -----------------------
   -- Validate_NMTOKENS --
   -----------------------

   function Validate_NMTOKENS
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol is
   begin
      if not Is_Valid_Nmtokens (Ch, XML_Version) then
         return Find (Symbols, "Invalid NMTOKENS: """ & Ch & """");
      end if;

      return Validate_List_Facets
        (Descr, Symbols, Ch, Descr.String_Length,
         Descr.String_Min_Length, Descr.String_Max_Length);
   end Validate_NMTOKENS;

   -------------------
   -- Validate_Name --
   -------------------

   function Validate_Name
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol is
   begin
      if not Is_Valid_Name (Ch, XML_Version) then
         return Find (Symbols, "Invalid Name: """ & Ch & """");
      end if;
      return Validate_String (Descr, Symbols, Ch);
   end Validate_Name;

   ---------------------
   -- Validate_NCName --
   ---------------------

   function Validate_NCName
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol is
   begin
      if not Is_Valid_NCname (Ch, XML_Version) then
         return Find (Symbols, "Invalid NCName: """ & Ch & """");
      end if;
      return Validate_String (Descr, Symbols, Ch);
   end Validate_NCName;

   ----------------------
   -- Validate_NCNames --
   ----------------------

   function Validate_NCNames
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol is
   begin
      if not Is_Valid_NCnames (Ch, XML_Version) then
         return Find (Symbols, "Invalid NCName: """ & Ch & """");
      end if;

      return Validate_List_Facets
        (Descr, Symbols, Ch, Descr.String_Length,
         Descr.String_Min_Length, Descr.String_Max_Length);
   end Validate_NCNames;

   -----------------------
   -- Validate_Language --
   -----------------------

   function Validate_Language
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol is
   begin
      if not Is_Valid_Language_Name (Ch) then
         return Find (Symbols, "Invalid language: """ & Ch & """");
      end if;
      return Validate_String (Descr, Symbols, Ch);
   end Validate_Language;

   --------------------
   -- Validate_QName --
   --------------------

   function Validate_QName
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      XML_Version   : XML_Versions) return Symbol is
   begin
      if not Is_Valid_QName (Ch, XML_Version) then
         return Find (Symbols, "Invalid QName: """ & Ch & """");
      end if;
      return Validate_String (Descr, Symbols, Ch);
   end Validate_QName;

   ------------------
   -- Validate_URI --
   ------------------

   function Validate_URI
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol is
   begin
      if not Is_Valid_URI (Ch) then
         return Find (Symbols, "Invalid anyURI: """ & Ch & """");
      end if;
      return Validate_String (Descr, Symbols, Ch);
   end Validate_URI;

   ---------------------
   -- Validate_String --
   ---------------------

   function Validate_String
     (Descr   : Simple_Type_Descr;
      Symbols : Sax.Utils.Symbol_Table;
      Ch      : Unicode.CES.Byte_Sequence) return Symbol
   is
      function Internal_Facets is new Validate_Length_Facets
        (Encoding.Length.all);
   begin
      return Internal_Facets
        (Symbols, Ch, Descr.Mask, Descr.String_Length,
         Descr.String_Min_Length, Descr.String_Max_Length);
   end Validate_String;

   -----------------------
   -- Validate_Notation --
   -----------------------

   function Validate_Notation
     (Notations     : Symbol_Htable.HTable;
      Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Error : Symbol;
   begin
      Error := Validate_String (Descr, Symbols, Ch);
      if Error /= No_Symbol then
         return Error;
      end if;

      if Symbol_Htable.Get (Notations, Find (Symbols, Ch)) = No_Symbol then
         Error := Find
           (Symbols, "NOTATION """ & Ch & """ undefined in this document");
      end if;

      return Error;
   end Validate_Notation;

   ------------------------
   -- Validate_HexBinary --
   ------------------------

   function Validate_HexBinary
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol is
   begin
      if Encoding.Length (Ch) mod 2 /= 0 then
         return Find
           (Symbols, "HexBinary length must be an even number of characters");
      end if;

      if not Is_Valid_HexBinary (Ch) then
         return Find (Symbols, "Invalid hexBinary: """ & Ch & """");
      end if;

      return Validate_HexBinary_Facets
        (Symbols, Ch, Descr.Mask,
        Descr.String_Length, Descr.String_Min_Length, Descr.String_Max_Length);
   end Validate_HexBinary;

   ---------------------------
   -- Validate_Base64Binary --
   ---------------------------

   function Validate_Base64Binary
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol is
   begin
      if not Is_Valid_Base64Binary (Ch) then
         return Find (Symbols, "Invalid base64Binary: """ & Ch & """");
      end if;

      return Validate_Base64Binary_Facets
        (Symbols, Ch, Descr.Mask,
        Descr.String_Length, Descr.String_Min_Length, Descr.String_Max_Length);
   end Validate_Base64Binary;

   --------------------------
   -- Validate_List_Facets --
   --------------------------

   function Validate_List_Facets
     (Descr   : Simple_Type_Descr;
      Symbols : Sax.Utils.Symbol_Table;
      Ch      : Unicode.CES.Byte_Sequence;
      Length, Min_Length, Max_Length : Integer) return Symbol
   is
      function List_Get_Length
        (Value : Unicode.CES.Byte_Sequence) return Natural;
      function List_Get_Length
        (Value : Unicode.CES.Byte_Sequence) return Natural
      is
         Length : Natural := 0;
         C      : Unicode_Char;
         Index  : Natural := Value'First;
      begin
         if Value = "" then
            return 0;
         end if;

         while Index <= Value'Last loop
            Encoding.Read (Value, Index, C);
            while C = Unicode.Names.Basic_Latin.Space loop
               Length := Length + 1;
               Encoding.Read (Value, Index, C);
            end loop;
         end loop;

         return Length + 1;
      end List_Get_Length;

      L : Natural;
   begin
      if Descr.Mask (Facet_Length)
        or else Descr.Mask (Facet_Min_Length)
        or else Descr.Mask (Facet_Max_Length)
      then
         L := List_Get_Length (Ch);
      else
         return No_Symbol;
      end if;

      if Descr.Mask (Facet_Length) then
         if L /= Length then
            return Find
              (Symbols,
               "Invalid size, must have"
               & Integer'Image (Length) & " items");
         end if;
      end if;

      if Descr.Mask (Facet_Min_Length) then
         if L < Min_Length then
            return Find
              (Symbols,
               "Not enough items, minimum number is"
               & Integer'Image (Min_Length));
         end if;
      end if;

      if Descr.Mask (Facet_Max_Length) then
         if L > Max_Length then
            return Find
              (Symbols,
               "Too many items, maximum number is"
               & Integer'Image (Max_Length));
         end if;
      end if;

      return No_Symbol;
   end Validate_List_Facets;

   -------------------
   -- Boolean_Value --
   -------------------

   procedure Boolean_Value
     (Symbols : Symbol_Table;
      Ch      : Byte_Sequence;
      Val     : out Boolean;
      Error   : out Symbol)
   is
      First : Integer;
      Index : Integer;
      C     : Unicode_Char;
   begin
      Val := False;

      if Ch = "" then
         Error := Find (Symbols, "Invalid value for boolean type: """"");
         return;
      end if;

      --  Check we do have a valid boolean representation (skip leading spaces)

      First := Ch'First;

      while First <= Ch'Last loop
         Index := First;
         Encoding.Read (Ch, First, C);
         exit when not Is_White_Space (C);
      end loop;

      if C = Digit_Zero or C = Digit_One then
         Val := C = Digit_One;
         if First <= Ch'Last then
            Encoding.Read (Ch, First, C);
         end if;

      elsif Index + True_Sequence'Length - 1 <= Ch'Last
        and then Ch (Index .. Index + True_Sequence'Length - 1) = True_Sequence
      then
         First := Index + True_Sequence'Length;
         Val := True;

      elsif Index + False_Sequence'Length - 1 <= Ch'Last
        and then Ch (Index .. Index + False_Sequence'Length - 1) =
          False_Sequence
      then
         First := Index + False_Sequence'Length;
         Val := False;

      else
         Error := Find
           (Symbols, "Invalid value for boolean type: """ & Ch & """");
         return;
      end if;

      --  Skip trailing spaces

      while First <= Ch'Last loop
         Encoding.Read (Ch, First, C);
         if not Is_White_Space (C) then
            Error := Find
              (Symbols, "Invalid value for boolean type: """ & Ch & """");
            return;
         end if;
      end loop;

      Error := No_Symbol;
   end Boolean_Value;

   ----------------------
   -- Validate_Boolean --
   ----------------------

   function Validate_Boolean
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      pragma Unreferenced (Descr);
      Val   : Boolean;
      Error : Symbol;
   begin
      Boolean_Value (Symbols, Ch, Val, Error);
      if Error /= No_Symbol then
         return Error;
      end if;

      return No_Symbol;
   end Validate_Boolean;

   -----------
   -- Value --
   -----------

   procedure Value (Symbols : Symbol_Table;
                    Ch      : Byte_Sequence;
                    Val     : out XML_Float;
                    Error   : out Symbol) is
   begin
      begin
         Val := Value (Ch);
      exception
         when Constraint_Error =>
            Error := Find (Symbols, "Invalid value: """ & Ch & """");
            return;
      end;
      Error := No_Symbol;
   end Value;

   --------------------
   -- Validate_Range --
   --------------------

   procedure Validate_Range
     (Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence;
      Mask          : Facets_Mask;
      Min_Inclusive : T;
      Min_Exclusive : T;
      Max_Inclusive : T;
      Max_Exclusive : T;
      Error         : out Symbol;
      Val           : out T) is
   begin
      Value
        (Symbols  => Symbols,
         Ch       => Ch,
         Val      => Val,
         Error    => Error);
      if Error /= No_Symbol then
         return;
      end if;

      if Mask (Facet_Min_Inclusive) then
         if Val < Min_Inclusive then
            Error := Find
              (Symbols,
               Ch & " is smaller than minInclusive ("
               & Image (Min_Inclusive) & ")");
            return;
         end if;
      end if;

      if Mask (Facet_Min_Exclusive) then
         if Val <= Min_Exclusive then
            Error := Find
              (Symbols,
               Ch & " is smaller than minExclusive ("
               & Image (Min_Exclusive) & ")");
            return;
         end if;
      end if;

      if Mask (Facet_Max_Inclusive) then
         if Max_Inclusive < Val then
            Error := Find
              (Symbols,
               Ch & " is greater than maxInclusive ("
               & Image (Max_Inclusive) & ")");
            return;
         end if;
      end if;

      if Mask (Facet_Max_Exclusive) then
         if Max_Exclusive <= Val then
            Error := Find
              (Symbols,
               Ch & " is greater than maxExclusive ("
               & Image (Max_Exclusive) & ")");
            return;
         end if;
      end if;
   end Validate_Range;

   procedure Validate_Double_Facets is new Validate_Range (XML_Float);
   procedure Validate_Decimal_Facets is new Validate_Range
     (Arbitrary_Precision_Number, Value => Value_No_Exponent);
   procedure Validate_Duration_Facets is new Validate_Range (Duration_T);
   procedure Validate_Date_Time_Facets is new Validate_Range (Date_Time_T);
   procedure Validate_Date_Facets is new Validate_Range (Date_T);
   procedure Validate_Time_Facets is new Validate_Range (Time_T);
   procedure Validate_GDay_Facets is new Validate_Range (GDay_T);
   procedure Validate_GMonth_Day_Facets is new Validate_Range (GMonth_Day_T);
   procedure Validate_GMonth_Facets is new Validate_Range (GMonth_T);
   procedure Validate_GYear_Facets is new Validate_Range (GYear_T);
   procedure Validate_GYear_Month_Facets is new Validate_Range
     (GYear_Month_T);

   ---------------------
   -- Validate_Double --
   ---------------------

   function Validate_Double
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : XML_Float;
      Error : Symbol;
   begin
      Validate_Double_Facets
        (Symbols, Ch, Descr.Mask, Descr.Float_Min_Inclusive,
         Descr.Float_Min_Exclusive, Descr.Float_Max_Inclusive,
         Descr.Float_Max_Exclusive, Error, Val);
      return Error;
   end Validate_Double;

   -----------------------
   -- Validate_Duration --
   -----------------------

   function Validate_Duration
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : Duration_T;
      Error : Symbol;
   begin
      Validate_Duration_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.Duration_Min_Inclusive, Descr.Duration_Min_Exclusive,
         Descr.Duration_Max_Inclusive, Descr.Duration_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_Duration;

   ------------------------
   -- Validate_Date_Time --
   ------------------------

   function Validate_Date_Time
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : Date_Time_T;
      Error : Symbol;
   begin
      Validate_Date_Time_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.DateTime_Min_Inclusive, Descr.DateTime_Min_Exclusive,
         Descr.DateTime_Max_Inclusive, Descr.DateTime_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_Date_Time;

   -------------------
   -- Validate_Date --
   -------------------

   function Validate_Date
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : Date_T;
      Error : Symbol;
   begin
      Validate_Date_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.Date_Min_Inclusive, Descr.Date_Min_Exclusive,
         Descr.Date_Max_Inclusive, Descr.Date_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_Date;

   -------------------
   -- Validate_Time --
   -------------------

   function Validate_Time
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : Time_T;
      Error : Symbol;
   begin
      Validate_Time_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.Time_Min_Inclusive, Descr.Time_Min_Exclusive,
         Descr.Time_Max_Inclusive, Descr.Time_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_Time;

   -------------------
   -- Validate_GDay --
   -------------------

   function Validate_GDay
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : GDay_T;
      Error : Symbol;
   begin
      Validate_GDay_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.GDay_Min_Inclusive, Descr.GDay_Min_Exclusive,
         Descr.GDay_Max_Inclusive, Descr.GDay_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_GDay;

   -------------------------
   -- Validate_GMonth_Day --
   -------------------------

   function Validate_GMonth_Day
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : GMonth_Day_T;
      Error : Symbol;
   begin
      Validate_GMonth_Day_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.GMonthDay_Min_Inclusive, Descr.GMonthDay_Min_Exclusive,
         Descr.GMonthDay_Max_Inclusive, Descr.GMonthDay_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_GMonth_Day;

   ---------------------
   -- Validate_GMonth --
   ---------------------

   function Validate_GMonth
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : GMonth_T;
      Error : Symbol;
   begin
      Validate_GMonth_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.GMonth_Min_Inclusive, Descr.GMonth_Min_Exclusive,
         Descr.GMonth_Max_Inclusive, Descr.GMonth_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_GMonth;

   --------------------
   -- Validate_GYear --
   --------------------

   function Validate_GYear
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : GYear_T;
      Error : Symbol;
   begin
      Validate_GYear_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.GYear_Min_Inclusive, Descr.GYear_Min_Exclusive,
         Descr.GYear_Max_Inclusive, Descr.GYear_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_GYear;

   --------------------------
   -- Validate_GYear_Month --
   --------------------------

   function Validate_GYear_Month
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Val   : GYear_Month_T;
      Error : Symbol;
   begin
      Validate_GYear_Month_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.GYearMonth_Min_Inclusive, Descr.GYearMonth_Min_Exclusive,
         Descr.GYearMonth_Max_Inclusive, Descr.GYearMonth_Max_Exclusive,
         Error, Val);
      return Error;
   end Validate_GYear_Month;

   ----------------------
   -- Validate_Decimal --
   ----------------------

   function Validate_Decimal
     (Descr         : Simple_Type_Descr;
      Symbols       : Sax.Utils.Symbol_Table;
      Ch            : Unicode.CES.Byte_Sequence) return Symbol
   is
      Error : Symbol;
      Val   : Arbitrary_Precision_Number;
   begin
      Validate_Decimal_Facets
        (Symbols, Ch, Descr.Mask,
         Descr.Decimal_Min_Inclusive, Descr.Decimal_Min_Exclusive,
         Descr.Decimal_Max_Inclusive, Descr.Decimal_Max_Exclusive,
        Error, Val);
      if Error /= No_Symbol then
         return Error;
      end if;

      return Check_Digits
        (Symbols         => Symbols,
         Num             => Val,
         Fraction_Digits => Descr.Fraction_Digits,
         Total_Digits    => Descr.Total_Digits);
   end Validate_Decimal;

   --------------------
   -- Convert_Regexp --
   --------------------

   function Convert_Regexp
     (Regexp : Unicode.CES.Byte_Sequence) return String
   is
      Result : Unbounded_String;
      Tmp    : Unbounded_String;
      Pos    : Integer := Regexp'First;
      C      : Character;

      function Next_Char return Character;
      --  Read the next char from the regexp, and check it is ASCII

      function Next_Char return Character is
         Char   : Unicode_Char;
      begin
         Encoding.Read (Regexp, Pos, Char);

         if Char > 127 then
            Raise_Exception
              (XML_Not_Implemented'Identity,
               "Unicode regexps are not supported");
         end if;

         return Character'Val (Integer (Char));
      end Next_Char;

   begin
      while Pos <= Regexp'Last loop
         C := Next_Char;

         if C = '[' then
            Append (Result, C);
            Tmp := Null_Unbounded_String;

            while Pos <= Regexp'Last loop
               C := Next_Char;

               if C = ']' then
                  Append (Tmp, C);
                  exit;

               elsif C = '\' and then Pos <= Regexp'Last then
                  C := Next_Char;

                  case C is
                     when 'i' =>
                        --  rule [99] in XMLSchema specifications
                        Append (Tmp, "A-Za-z:_");

                     when 'c' =>
                        Append (Tmp, "a-z:A-Z0-9._-");

                     when 'w' =>
                        Append (Tmp, "a-zA-Z0-9`");

                     when 'I' | 'C' | '?' =>
                        Raise_Exception
                          (XML_Not_Implemented'Identity,
                           "Unsupported regexp construct: \" & C);

                     when 'P' | 'p' =>
                        if Pos <= Regexp'Last
                          and then Regexp (Pos) = '{'
                        then
                           Raise_Exception
                             (XML_Not_Implemented'Identity,
                              "Unsupported regexp construct: \P{...}");
                        else
                           Append (Tmp, '\' & C);
                        end if;

                     when others =>
                        Append (Tmp, '\' & C);
                  end case;

               else
                  if C = '-'
                    and then Pos <= Regexp'Last
                    and then Regexp (Pos) = '['
                  then
                     Raise_Exception
                       (XML_Not_Implemented'Identity,
                        "Unsupported regexp construct: [...-[...]]");
                  end if;

                  Append (Tmp, C);
               end if;
            end loop;

            Append (Result, Tmp);

         --  ??? Some tests in the old w3c testsuite seem to imply that
         --  \c and \i are valid even outside character classes. Not sure about
         --  this though

         elsif C = '\' and then Pos <= Regexp'Last then
            C := Next_Char;

            case C is
               when 'i' =>
                  --  rule [99] in XMLSchema specifications
                  Append (Result, "[A-Za-z:_]");

               when 'c' =>
                  Append (Result, "[a-z:A-Z0-9._-]");

               when 'w' =>
                  Append (Result, "[a-zA-Z0-9`]");

               when 'I' | 'C' =>
                  Raise_Exception
                    (XML_Not_Implemented'Identity,
                     "Unsupported regexp construct: \" & C);

               when 'P' | 'p' =>
                  if Pos <= Regexp'Last
                    and then Regexp (Pos) = '{'
                  then
                     Raise_Exception
                       (XML_Not_Implemented'Identity,
                        "Unsupported regexp construct: \P{...}");
                  else
                     Append (Result, '\' & C);
                  end if;

               when others =>
                  Append (Result, '\' & C);
            end case;

         else
            Append (Result, C);
         end if;
      end loop;

      return Anchor (To_String (Result));
   end Convert_Regexp;

   ------------------------
   -- Missing_End_Anchor --
   ------------------------

   function Missing_End_Anchor (Str : String) return Boolean is
   begin
      --  Do not add '$' if Str ends with a single \, since it is
      --  invalid anyway
      return Str'Length = 0
        or else
          (Str (Str'Last) /= '$'
           and then (Str (Str'Last) /= '\'
                     or else (Str'Length /= 1
                              and then Str (Str'Last - 1) = '\')));
   end Missing_End_Anchor;

   --------------------------
   -- Missing_Start_Anchor --
   --------------------------

   function Missing_Start_Anchor (Str : String) return Boolean is
   begin
      --  Do not add '^' if we start with an operator, since Str is invalid
      return Str'Length = 0
        or else not (Str (Str'First) = '^'
                     or else Str (Str'First) = '*'
                     or else Str (Str'First) = '+'
                     or else Str (Str'First) = '?');
   end Missing_Start_Anchor;

   ------------
   -- Anchor --
   ------------

   function Anchor (Str : String) return String is
      Start : constant Boolean := Missing_Start_Anchor (Str);
      Last  : constant Boolean := Missing_End_Anchor (Str);
   begin
      if Start and Last then
         return "^(" & Str & ")$";
      elsif Start then
         return "^" & Str;
      elsif Last then
         return Str & "$";
      else
         return Str;
      end if;
   end Anchor;

   ---------------
   -- Add_Facet --
   ---------------

   procedure Add_Facet
     (Facets       : in out All_Facets;
      Symbols      : Sax.Utils.Symbol_Table;
      Enumerations : in out Enumeration_Tables.Instance;
      Facet_Name   : Sax.Symbols.Symbol;
      Value        : Sax.Symbols.Symbol;
      Loc          : Sax.Locators.Location)
   is
      Val : Symbol;
   begin
      if Get (Facet_Name).all = "pattern" then
         --  Do not normalize the value
         if Facets (Facet_Pattern).Value /= No_Symbol then
            Facets (Facet_Pattern) :=
              (Find
                 (Symbols,
                  '(' & Get (Facets (Facet_Pattern).Value).all
                  & ")|(" & Get (Value).all & ')'),
               No_Enumeration_Index, Loc);
         else
            Facets (Facet_Pattern) := (Value, No_Enumeration_Index, Loc);
         end if;

         return;
      end if;

      Val := Find
        (Symbols, Trim (Get (Value).all, Ada.Strings.Both));

      if Get (Facet_Name).all = "whiteSpace" then
         Facets (Facet_Whitespace) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "enumeration" then
         Append (Enumerations, (Value => Val,
                                Next  => Facets (Facet_Enumeration).Enum));
         Facets (Facet_Enumeration) := (No_Symbol, Last (Enumerations), Loc);
      elsif Get (Facet_Name).all = "minInclusive" then
         Facets (Facet_Min_Inclusive) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "maxInclusive" then
         Facets (Facet_Max_Inclusive) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "minExclusive" then
         Facets (Facet_Min_Exclusive) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "maxExclusive" then
         Facets (Facet_Max_Exclusive) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "length" then
         Facets (Facet_Length) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "minLength" then
         Facets (Facet_Min_Length) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "maxLength" then
         Facets (Facet_Max_Length) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "totalDigits" then
         Facets (Facet_Total_Digits) := (Val, No_Enumeration_Index, Loc);
      elsif Get (Facet_Name).all = "fractionDigits" then
         Facets (Facet_Fraction_Digits) := (Val, No_Enumeration_Index, Loc);
      else
         pragma Assert (False, "Invalid facet:");
         null;
      end if;
   end Add_Facet;

   ---------------------------------
   -- Override_Single_Range_Facet --
   ---------------------------------

   procedure Override_Single_Range_Facet
     (Symbols       : Sax.Utils.Symbol_Table;
      Facets        : All_Facets;
      Facet         : Facet_Enum;
      Mask          : in out Facets_Mask;
      Val           : in out T;
      Error         : in out Symbol;
      Error_Loc     : in out Location) is
   begin
      if Error = No_Symbol and then Facets (Facet) /= No_Facet_Value then
         Value
           (Symbols,
            Ch    => Get (Facets (Facet).Value).all,
            Val   => Val,
            Error => Error);
         if Error /= No_Symbol then
            Error_Loc := Facets (Facet).Loc;
         else
            Mask (Facet) := True;
         end if;
      end if;
   end Override_Single_Range_Facet;

   ---------------------------
   -- Override_Range_Facets --
   ---------------------------

   procedure Override_Range_Facets
     (Symbols       : Sax.Utils.Symbol_Table;
      Facets        : All_Facets;
      Mask          : in out Facets_Mask;
      Min_Inclusive : in out T;
      Min_Exclusive : in out T;
      Max_Inclusive : in out T;
      Max_Exclusive : in out T;
      Error         : out Symbol;
      Error_Loc     : out Location)
   is
      procedure Do_Override is new Override_Single_Range_Facet (T, Value);
   begin
      Do_Override (Symbols, Facets, Facet_Max_Inclusive, Mask,
                   Max_Inclusive, Error, Error_Loc);
      Do_Override (Symbols, Facets, Facet_Max_Exclusive, Mask,
                   Max_Exclusive, Error, Error_Loc);
      Do_Override (Symbols, Facets, Facet_Min_Inclusive, Mask,
                   Min_Inclusive, Error, Error_Loc);
      Do_Override (Symbols, Facets, Facet_Min_Exclusive, Mask,
                   Min_Exclusive, Error, Error_Loc);
   end Override_Range_Facets;

   procedure Override_Decimal_Facets
     is new Override_Range_Facets (Arbitrary_Precision_Number);
   procedure Override_Float_Facets is new Override_Range_Facets (XML_Float);
   procedure Override_Duration_Facets
     is new Override_Range_Facets (Duration_T);
   procedure Override_Date_Time_Facets
     is new Override_Range_Facets (Date_Time_T);
   procedure Override_Date_Facets is new Override_Range_Facets (Date_T);
   procedure Override_Time_Facets is new Override_Range_Facets (Time_T);
   procedure Override_GDay_Facets is new Override_Range_Facets (GDay_T);
   procedure Override_GMonth_Day_Facets
     is new Override_Range_Facets (GMonth_Day_T);
   procedure Override_GMonth_Facets is new Override_Range_Facets (GMonth_T);
   procedure Override_GYear_Facets is new Override_Range_Facets (GYear_T);
   procedure Override_GYear_Month_Facets
     is new Override_Range_Facets (GYear_Month_T);

   ----------------------------
   -- Override_Length_Facets --
   ----------------------------

   procedure Override_Length_Facets
     (Symbols       : Sax.Utils.Symbol_Table;
      Facets        : All_Facets;
      Mask          : in out Facets_Mask;
      Length        : in out Integer;
      Min_Length    : in out Integer;
      Max_Length    : in out Integer;
      Error         : out Symbol;
      Error_Loc     : out Location)
   is
   begin
      if Facets (Facet_Length) /= No_Facet_Value then
         begin
            Length := Natural'Value
              (Get (Facets (Facet_Length).Value).all);
            Mask (Facet_Length) := True;
         exception
            when Constraint_Error =>
               Error := Find
                 (Symbols, "Expecting integer for length facet");
               Error_Loc := Facets (Facet_Length).Loc;
         end;
      end if;

      if Facets (Facet_Min_Length) /= No_Facet_Value then
         begin
            Min_Length := Natural'Value
              (Get (Facets (Facet_Min_Length).Value).all);
            Mask (Facet_Min_Length) := True;
         exception
            when Constraint_Error =>
               Error := Find
                 (Symbols, "Expecting integer for minLength facet");
               Error_Loc := Facets (Facet_Min_Length).Loc;
         end;
      end if;

      if Facets (Facet_Max_Length) /= No_Facet_Value then
         begin
            Max_Length := Natural'Value
              (Get (Facets (Facet_Max_Length).Value).all);
            Mask (Facet_Max_Length) := True;
         exception
            when Constraint_Error =>
               Error := Find
                 (Symbols, "Expecting integer for maxlength facet");
               Error_Loc := Facets (Facet_Max_Length).Loc;
         end;
      end if;
   end Override_Length_Facets;

   --------------
   -- Override --
   --------------

   procedure Override
     (Simple     : in out Simple_Type_Descr;
      Facets     : All_Facets;
      Symbols    : Sax.Utils.Symbol_Table;
      As_Restriction : Boolean;
      Error      : out Sax.Symbols.Symbol;
      Error_Loc  : out Sax.Locators.Location)
   is
      function Compile_Regexp (Str : Symbol) return Pattern_Matcher_Access;

      function Compile_Regexp (Str : Symbol) return Pattern_Matcher_Access is
         Convert : constant String := Convert_Regexp (Get (Str).all);
      begin
         if Debug then
            Debug_Output ("Compiling regexp as " & Convert);
         end if;

         return new Pattern_Matcher'(Compile (Convert));
      exception
         when  GNAT.Regpat.Expression_Error =>
            Error_Loc := Facets (Facet_Pattern).Loc;
            Error := Find
              (Symbols,
               "Invalid regular expression "
               & Get (Str).all & " (converted to " & Convert & ")");
            return null;
      end Compile_Regexp;

      Val : Symbol;
      Tmp : Pattern_Matcher_Array_Access;
   begin
      if Facets (Facet_Whitespace) /= No_Facet_Value then
         Val := Facets (Facet_Whitespace).Value;
         if Get (Val).all = "preserve" then
            Simple.Whitespace := Preserve;
         elsif Get (Val).all = "replace" then
            Simple.Whitespace := Replace;
         elsif Get (Val).all = "collapse" then
            Simple.Whitespace := Collapse;
         else
            Error_Loc := Facets (Facet_Whitespace).Loc;
            Error := Find (Symbols, "Invalid value for whiteSpace facet: "
                           & Get (Val).all);
            return;
         end if;

         Simple.Mask (Facet_Whitespace) := True;
      end if;

      if Facets (Facet_Pattern) /= No_Facet_Value then
         Val := Facets (Facet_Pattern).Value;

         begin
            if As_Restriction then
               --  We must match all patterns (from base and from extension),
               --  and we cannot combine them. So we need to add one more
               --  pattern to the facets.
               --  [Simple] is a copy of the base type, and will be the new
               --  restriction on exit.

               if Simple.Pattern = null then
                  Simple.Pattern := new Pattern_Matcher_Array (1 .. 1);
               else
                  Tmp := Simple.Pattern;
                  Simple.Pattern :=
                    new Pattern_Matcher_Array (Tmp'First .. Tmp'Last + 1);
                  Simple.Pattern (Tmp'Range) := Tmp.all;
                  Unchecked_Free (Tmp);
               end if;

               Simple.Pattern (Simple.Pattern'Last) :=
                 (Str     => Val,
                  Pattern => Compile_Regexp (Val));

            else
               --  We must combine the base's patterns with the extension's
               --  pattern, since the type must match either of those.
               --  The number of patterns does not change

               if Simple.Pattern = null then
                  Simple.Pattern := new Pattern_Matcher_Array'
                    (1 => (Str => Val, Pattern => Compile_Regexp (Val)));
               else
                  for P in Simple.Pattern'Range loop
                     Simple.Pattern (P).Str := Find
                       (Symbols,
                        '(' & Get (Simple.Pattern (P).Str).all
                        & ")|(" & Get (Val).all & ')');
                     Unchecked_Free (Simple.Pattern (P).Pattern);
                     Simple.Pattern (P).Pattern :=
                       Compile_Regexp (Simple.Pattern (P).Str);
                  end loop;
               end if;
            end if;

         exception
            when E : XML_Not_Implemented =>
               Error := Find (Symbols, '#' & Exception_Message (E));
               Free (Simple.Pattern);
         end;

         if Error /= No_Symbol then
            Error_Loc := Facets (Facet_Pattern).Loc;
            return;
         end if;

         Simple.Mask (Facet_Pattern) := True;
      end if;

      if Facets (Facet_Enumeration) /= No_Facet_Value then
         Simple.Enumeration := Facets (Facet_Enumeration).Enum;
         Simple.Mask (Facet_Enumeration) := True;
      end if;

      Error := No_Symbol;

      case Simple.Kind is
         when Primitive_Union =>
            null;

         when Primitive_List =>
            Override_Length_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.List_Length, Simple.List_Min_Length,
               Simple.List_Max_Length, Error, Error_Loc);

         when Primitive_String .. Primitive_HexBinary =>
            Override_Length_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.String_Length, Simple.String_Min_Length,
               Simple.String_Max_Length, Error, Error_Loc);

         when Primitive_Boolean =>
            null;

         when Primitive_Float | Primitive_Double  =>
            Override_Float_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.Float_Min_Inclusive, Simple.Float_Min_Exclusive,
               Simple.Float_Max_Inclusive, Simple.Float_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_Decimal =>
            Override_Decimal_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.Decimal_Min_Inclusive, Simple.Decimal_Min_Exclusive,
               Simple.Decimal_Max_Inclusive, Simple.Decimal_Max_Exclusive,
               Error, Error_Loc);

            if Error = No_Symbol then
               if Facets (Facet_Total_Digits) /= No_Facet_Value then
                  begin
                     Simple.Total_Digits := Positive'Value
                       (Get (Facets (Facet_Total_Digits).Value).all);
                     Simple.Mask (Facet_Total_Digits) := True;
                  exception
                     when Constraint_Error =>
                        Error := Find
                          (Symbols, "Expecting integer for totalDigits facet");
                        Error_Loc := Facets (Facet_Total_Digits).Loc;
                  end;
               end if;

               if Facets (Facet_Fraction_Digits) /= No_Facet_Value then
                  begin
                     Simple.Fraction_Digits := Natural'Value
                       (Get (Facets (Facet_Fraction_Digits).Value).all);
                     Simple.Mask (Facet_Fraction_Digits) := True;
                  exception
                     when Constraint_Error =>
                        Error := Find
                          (Symbols,
                           "Expecting integer for fractionDigits facet");
                        Error_Loc := Facets (Facet_Fraction_Digits).Loc;
                  end;
               end if;

               if Simple.Fraction_Digits /= Natural'Last
                 and then Simple.Total_Digits /= Positive'Last
                 and then Simple.Fraction_Digits > Simple.Total_Digits
               then
                  Error_Loc := Facets (Facet_Fraction_Digits).Loc;
                  Error := Find
                    (Symbols,
                     "fractionDigits cannot be greater than totalDigits");
               end if;
            end if;

         when Primitive_Time =>
            Override_Time_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.Time_Min_Inclusive, Simple.Time_Min_Exclusive,
               Simple.Time_Max_Inclusive, Simple.Time_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_DateTime =>
            Override_Date_Time_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.DateTime_Min_Inclusive, Simple.DateTime_Min_Exclusive,
               Simple.DateTime_Max_Inclusive, Simple.DateTime_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_GDay =>
            Override_GDay_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.GDay_Min_Inclusive, Simple.GDay_Min_Exclusive,
               Simple.GDay_Max_Inclusive, Simple.GDay_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_GMonthDay =>
            Override_GMonth_Day_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.GMonthDay_Min_Inclusive, Simple.GMonthDay_Min_Exclusive,
               Simple.GMonthDay_Max_Inclusive, Simple.GMonthDay_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_GMonth =>
            Override_GMonth_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.GMonth_Min_Inclusive, Simple.GMonth_Min_Exclusive,
               Simple.GMonth_Max_Inclusive, Simple.GMonth_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_GYearMonth =>
            Override_GYear_Month_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.GYearMonth_Min_Inclusive,
               Simple.GYearMonth_Min_Exclusive,
               Simple.GYearMonth_Max_Inclusive,
               Simple.GYearMonth_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_GYear =>
            Override_GYear_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.GYear_Min_Inclusive, Simple.GYear_Min_Exclusive,
               Simple.GYear_Max_Inclusive, Simple.GYear_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_Date =>
            Override_Date_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.Date_Min_Inclusive, Simple.Date_Min_Exclusive,
               Simple.Date_Max_Inclusive, Simple.Date_Max_Exclusive,
               Error, Error_Loc);

         when Primitive_Duration =>
            Override_Duration_Facets
              (Symbols, Facets, Simple.Mask,
               Simple.Duration_Min_Inclusive, Simple.Duration_Min_Exclusive,
               Simple.Duration_Max_Inclusive, Simple.Duration_Max_Exclusive,
               Error, Error_Loc);
      end case;

      --  ??? Should detect unused facets and report errors
   end Override;

   --------------------------
   -- Normalize_Whitespace --
   --------------------------

   procedure Normalize_Whitespace
     (Whitespace : Schema.Simple_Types.Whitespace_Restriction;
      Val        : in out Unicode.CES.Byte_Sequence;
      Last       : in out Natural)
   is
   begin
      case Whitespace is
         when Preserve =>
            return;  --  Nothing to do

         when Replace =>
            declare
               Idx   : Natural := Val'First;
               First : Natural := Last + 1;
               C     : Unicode_Char;
            begin
               while Idx <= Last loop
                  First := Idx;
                  Encoding.Read (Val, Idx, C);

                  if Is_White_Space (C) then
                     --  Assumes all characters we replace are encoded as
                     --  single byte
                     Val (First) := ' ';
                  end if;
               end loop;

               --  Length of string does not change
            end;

         when Collapse =>
            if Val = "" then
               return;  --  nothing to do
            end if;

            declare
               C       : Unicode_Char;
               Idx, Idx_Output : Natural := Val'First;
               First   : Natural := Last + 1;
               Tmp     : Natural;
               Last_Space : Natural := Last + 1;
               Prev_Is_Whitespace : Boolean := False;
            begin
               --  Remove leading spaces.

               loop
                  First := Idx;
                  Encoding.Read (Val, Idx, C);
                  exit when not Is_White_Space (C);

                  if Idx > Last then
                     Last := 0;
                     return;  --  Empty string
                  end if;
               end loop;

               if First /= Val'First then
                  Val (Val'First .. Last - First + Val'First) :=
                    Val (First .. Last);
                  Last := Last - First + Val'First;
               end if;

               Idx := Val'First;
               Idx_Output := Val'First;

               --  Iterate and replace all whitespaces. Mark the spot of the
               --  last whitespace so that we can ignore trailing spaces.
               --  At the same time, we can copy to Idx_Output, since the
               --  output string will always be at least as short as Val.

               while Idx <= Last loop
                  Tmp := Idx;
                  Encoding.Read (Val, Idx, C);

                  --  Copy, if needed, the character we just read
                  if Is_White_Space (C) then
                     if not Prev_Is_Whitespace then
                        Val (Idx_Output) := ' ';
                        Last_Space := Idx_Output;
                        Idx_Output := Idx_Output + 1;
                        Prev_Is_Whitespace := True;
                     end if;
                  else
                     Val (Idx_Output .. Idx_Output + Idx - Tmp - 1) :=
                       Val (Tmp .. Idx - 1);
                     Idx_Output := Idx_Output + Idx - Tmp;
                     Last_Space := Idx_Output;  --  after this char
                     Prev_Is_Whitespace := False;
                  end if;
               end loop;

               --  Now skip trailing whitespaces if any

               Last := Last_Space - 1;
            end;
      end case;
   end Normalize_Whitespace;

   ----------
   -- Copy --
   ----------

   function Copy (Descr : Simple_Type_Descr) return Simple_Type_Descr is
      Result : Simple_Type_Descr := Descr;
   begin
      if Descr.Pattern /= null then
         Result.Pattern := new Pattern_Matcher_Array (Descr.Pattern'Range);
         for P in Descr.Pattern'Range loop
            Result.Pattern (P) :=
              (Str     => Descr.Pattern (P).Str,
               Pattern => new Pattern_Matcher'(Descr.Pattern (P).Pattern.all));
         end loop;
      end if;
      return Result;
   end Copy;

   ----------
   -- Free --
   ----------

   procedure Free (Arr : in out Pattern_Matcher_Array_Access) is
   begin
      if Arr /= null then
         for A in Arr'Range loop
            Unchecked_Free (Arr (A).Pattern);
         end loop;
         Unchecked_Free (Arr);
      end if;
   end Free;

   -------------
   -- Get_Key --
   -------------

   function Get_Key (Id : Symbol) return Symbol is
   begin
      return Id;
   end Get_Key;

   ----------
   -- Free --
   ----------

   procedure Free (Symbol_Table : in out Symbol_Htable_Access) is
      procedure Unchecked_Free is new Ada.Unchecked_Deallocation
        (Symbol_Htable.HTable, Symbol_Htable_Access);
   begin
      if Symbol_Table /= null then
         Symbol_Htable.Reset (Symbol_Table.all);
         Unchecked_Free (Symbol_Table);
      end if;
   end Free;

   --------------
   -- Check_Id --
   --------------

   procedure Check_Id
     (Symbols   : Symbol_Table;
      Id_Table  : in out Symbol_Htable_Access;
      Value     : Unicode.CES.Byte_Sequence;
      Error     : in out Symbol)
   is
      Val : constant Symbol := Find (Symbols, Value);
   begin
      if Id_Table = null then
         Id_Table := new Symbol_Htable.HTable (101);
      else
         if Symbol_Htable.Get (Id_Table.all, Val) /= No_Symbol then
            Error := Find (Symbols, "ID """ & Value & """ already defined");
            return;
         end if;
      end if;

      Symbol_Htable.Set (Id_Table.all, Val);
      Error := No_Symbol;
   end Check_Id;

end Schema.Simple_Types;
