------------------------------------------------------------------------------
--                     XML/Ada - An XML suite for Ada95                     --
--                                                                          --
--                     Copyright (C) 2004-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.                            --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
-- 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.Characters.Handling;        use Ada.Characters.Handling;
with Ada.Exceptions;                 use Ada.Exceptions;
with Ada.Strings.Unbounded;          use Ada.Strings.Unbounded;
with Ada.Unchecked_Deallocation;
with GNAT.Task_Lock;
with Interfaces;                     use Interfaces;
with Sax.Attributes;                 use Sax.Attributes;
with Sax.Locators;                   use Sax.Locators;
with Sax.Symbols;                    use Sax.Symbols;
with Sax.Utils;                      use Sax.Utils;
with Schema.Simple_Types;            use Schema.Simple_Types;
with Schema.Validators.XSD_Grammar;  use Schema.Validators.XSD_Grammar;
with Unicode.CES;                    use Unicode.CES;
with Unicode;                        use Unicode;

package body Schema.Validators is
   use XML_Grammars, Attributes_Tables, Enumeration_Tables;

   function To_Graphic_String (Str : Byte_Sequence) return String;
   --  Convert non-graphic characters in Str to make them visible in a display

   type Attribute_Validator_Data is record
      Validator : Named_Attribute_List;  --  Index into the table
      Visited   : Boolean;
   end record;
   type Attribute_Validator_Index is new Natural;
   type Attribute_Validator_Array is array (Attribute_Validator_Index range <>)
     of Attribute_Validator_Data;
   function To_Attribute_Array
     (NFA        : access Schema_NFA'Class;
      Attributes : Attributes_List) return Attribute_Validator_Array;
   --  The data required to validate attributes

   procedure Create_Grammar_If_Needed
     (Grammar : in out XML_Grammar;
      Symbols : Symbol_Table := No_Symbol_Table);
   --  Create the grammar if needed
   --  Symbols is used only when a new grammar is created.

   procedure Validate_Attribute
     (Attr      : Attribute_Descr;
      Reader    : access Abstract_Validation_Reader'Class;
      Atts      : access Sax_Attribute_List;
      Index     : Natural);
   --  Validate the value of a single attribute

   procedure Reset_Simple_Types
     (NFA : access Schema_NFA'Class;
      To  : Simple_Type_Index := No_Simple_Type_Index);
   --  Resets the contents of G.Simple_Types by resizing the table and freeing
   --  needed data
   --  If [To] is [No_Simple_Type_Index], the table is freed

   function To_String (Any : Any_Descr) return String;
   --  Debug only

   ---------------
   -- To_String --
   ---------------

   function To_String (Any : Any_Descr) return String is
      Str : Unbounded_String;
   begin
      Append (Str, "{" & Any.Process_Contents'Img);
      if Any.Namespaces /= No_Symbol then
         Append (Str, " ns={" & Get (Any.Namespaces).all & "}");
      end if;
      if Any.No_Namespaces /= No_Symbol then
         Append (Str, " no_ns={" & Get (Any.No_Namespaces).all & "}");
      end if;
      return To_String (Str) & "}";
   end To_String;

   ----------------------
   -- Validation_Error --
   ----------------------

   procedure Validation_Error
     (Reader  : access Abstract_Validation_Reader;
      Message : Byte_Sequence;
      Loc     : Sax.Locators.Location := Sax.Locators.No_Location;
      Except  : Exception_Id := XML_Validation_Error'Identity) is
   begin
      if Debug then
         Debug_Output ("Validation_Error: " & Message);
      end if;

      if Loc /= No_Location then
         Reader.Error_Location := Loc;
      else
         Reader.Error_Location := Reader.Current_Location;
      end if;

      if Message (Message'First) = '#' then
         Reader.Error_Msg := Find_Symbol
           (Reader.all, Message (Message'First + 1 .. Message'Last));
         raise XML_Not_Implemented;
      else
         Reader.Error_Msg := Find_Symbol (Reader.all, Message);
         Raise_Exception (Except);
      end if;
   end Validation_Error;

   -----------------------
   -- Get_Error_Message --
   -----------------------

   function Get_Error_Message
     (Reader : Abstract_Validation_Reader) return Unicode.CES.Byte_Sequence
   is
      Loc : Location;
   begin
      if Reader.Error_Msg = No_Symbol then
         return "";

      else
         Loc := Reader.Error_Location;
         if Loc = No_Location then
            Loc := Reader.Current_Location;
         end if;

         declare
            Error : constant Cst_Byte_Sequence_Access :=
              Get (Reader.Error_Msg);
         begin
            if Loc /= No_Location then
               return To_String (Loc, Use_Basename_In_Error_Messages (Reader))
                 & ": " & Error.all;
            else
               return Error.all;
            end if;
         end;
      end if;
   end Get_Error_Message;

   -----------------------
   -- Add_Any_Attribute --
   -----------------------

   procedure Add_Any_Attribute
     (Grammar        : XML_Grammar;
      List           : in out Attributes_List;
      Any            : Internal_Any_Descr;
      As_Restriction : Boolean) is
   begin
      List.Any := Combine
        (Grammar        => Grammar,
         Base_Any       => List.Any,
         Local_Process  => Any.Process_Contents,
         Local          => Any.Namespaces,
         As_Restriction => As_Restriction,
         Target_NS      => Any.Target_NS);
   end Add_Any_Attribute;

   ----------------------------
   -- Normalize_And_Validate --
   ----------------------------

   procedure Normalize_And_Validate
     (Parser  : access Abstract_Validation_Reader'Class;
      Simple  : Schema.Simple_Types.Simple_Type_Index;
      Fixed   : in out Sax.Symbols.Symbol;
      Loc     : Sax.Locators.Location)
   is
   begin
      if Fixed /= No_Symbol
        and then Simple /= No_Simple_Type_Index
      then
         declare
            Simple_Descr : constant Simple_Type_Descr :=
                             Get_NFA (Parser.Grammar).Get_Simple_Type (Simple);
            Norm         : Byte_Sequence := Get (Fixed).all;
            Last         : Integer := Norm'Last;
         begin
            --  Normalize whitespaces, for faster comparison later
            --  on.

            if Simple_Descr.Mask (Facet_Whitespace) then
               Normalize_Whitespace
                 (Simple_Descr.Whitespace, Norm, Last);
               Fixed := Find
                 (Get_Symbol_Table (Parser.Grammar),
                  Norm (Norm'First .. Last));
            end if;

            Validate_Simple_Type
              (Reader      => Parser,
               Simple_Type => Simple,
               Ch          => Norm (Norm'First .. Last),
               Loc         => Loc,
               Insert_Id   => True);
         end;
      end if;
   end Normalize_And_Validate;

   -------------------
   -- Add_Attribute --
   -------------------

   procedure Add_Attribute
     (Parser    : access Abstract_Validation_Reader'Class;
      List      : in out Attributes_List;
      Attribute : Attribute_Descr;
      Ref       : Named_Attribute_List := Empty_Named_Attribute_List;
      Loc       : Sax.Locators.Location)
   is
      NFA  : constant Schema_NFA_Access := Get_NFA (Parser.Grammar);
      L    : Named_Attribute_List := List.Named;
      Tmp  : Named_Attribute_List;
      Attr : Attribute_Descr := Attribute;
   begin
      if Debug then
         Debug_Output
           ("Adding attribute " & To_QName (Attribute.Name)
            & " Use_Type=" & Attribute.Use_Type'Img
            & " local=" & Attribute.Is_Local'Img);
      end if;

      while L /= Empty_Named_Attribute_List loop
         if NFA.Attributes.Table (L).Name.Local = Attribute.Name.Local then
            if Debug then
               Debug_Output ("Override inherited attribute");
            end if;

            --  Override use_type, form,... from the <restriction>
            Tmp := NFA.Attributes.Table (L).Next;

            Attr := Attribute;
            Normalize_And_Validate
              (Parser,
               Attr.Simple_Type, Attr.Fixed, Loc);

            NFA.Attributes.Table (L) := Attr;
            NFA.Attributes.Table (L).Next := Tmp;

            return;
         end if;
         L := NFA.Attributes.Table (L).Next;
      end loop;

      if Ref /= Empty_Named_Attribute_List then
         Attr          := NFA.Attributes.Table (Ref);
         Attr.Use_Type := Attribute.Use_Type;
         Attr.Is_Local := Attribute.Is_Local;

         if Attribute.Fixed /= No_Symbol then
            Attr.Fixed    := Attribute.Fixed;
         end if;
      end if;

      Normalize_And_Validate
        (Parser,
         Attr.Simple_Type, Attr.Fixed, Loc);

      Append (NFA.Attributes, Attr);
      NFA.Attributes.Table (Last (NFA.Attributes)).Next := List.Named;
      List.Named := Last (NFA.Attributes);
   end Add_Attribute;

   --------------------
   -- Add_Attributes --
   --------------------

   procedure Add_Attributes
     (Parser         : access Abstract_Validation_Reader'Class;
      List           : in out Attributes_List;
      Attributes     : Attributes_List;
      As_Restriction : Boolean;
      Loc            : Sax.Locators.Location)
   is
      NFA : constant Schema_NFA_Access := Get_NFA (Parser.Grammar);
      L   : Named_Attribute_List := Attributes.Named;
   begin
      while L /= Empty_Named_Attribute_List loop
         Add_Attribute (Parser, List, NFA.Attributes.Table (L), Loc => Loc);
         L := NFA.Attributes.Table (L).Next;
      end loop;

      Add_Any_Attribute
        (Parser.Grammar, List,
         Internal_Any_Descr'
           (Target_NS        => Empty_String,
            Process_Contents => Attributes.Any.Process_Contents,
            Namespaces       => Attributes.Any.Namespaces),
         As_Restriction);
   end Add_Attributes;

   ------------------------
   -- To_Attribute_Array --
   ------------------------

   function To_Attribute_Array
     (NFA        : access Schema_NFA'Class;
      Attributes : Attributes_List) return Attribute_Validator_Array
   is
      Count : Attribute_Validator_Index := 0;
      L     : Named_Attribute_List := Attributes.Named;
   begin
      while L /= Empty_Named_Attribute_List loop
         Count := Count + 1;
         L := NFA.Attributes.Table (L).Next;
      end loop;

      declare
         Result : Attribute_Validator_Array (1 .. Count);
      begin
         Count := Result'First;
         L := Attributes.Named;
         while L /= Empty_Named_Attribute_List loop
            Result (Count) := (Validator => L,
                               Visited   => False);
            Count := Count + 1;
            L := NFA.Attributes.Table (L).Next;
         end loop;

         return Result;
      end;
   end To_Attribute_Array;

   -------------
   -- Combine --
   -------------

   function Combine
     (Grammar        : XML_Grammar;
      Base_Any       : Any_Descr;
      Local_Process  : Process_Contents_Type;
      Local          : Sax.Symbols.Symbol;
      As_Restriction : Boolean;
      Target_NS      : Sax.Symbols.Symbol) return Any_Descr
   is
      use Symbol_Htable;
      Namespaces    : Symbol_Htable.HTable (127);
      No_Namespaces : Symbol_Htable.HTable (127);
      Tmp           : Symbol_Htable.HTable (127);

      Result : Any_Descr;
      Base_Is_Any, Local_Is_Any : Boolean;

      Symbols : constant Symbol_Table := Get (Grammar).Symbols;

      procedure Callback (Str : Byte_Sequence);
      procedure Add_To_Table
        (Sym : Symbol; Table : in out Symbol_Htable.HTable);

      procedure Merge (Sym : Symbol);
      --  Take all namespaces in [Sym], and copy, in [Namespaces], those that
      --  are also in [Tmp], but not in [No_Namespaces]

      function To_Symbol (Table : Symbol_Htable.HTable) return Symbol;
      --  Return the list of strings in Table

      -----------
      -- Merge --
      -----------

      procedure Merge (Sym : Symbol) is

         procedure Callback (Str : Byte_Sequence);
         procedure Do_Merge (S : Symbol);

         --------------
         -- Do_Merge --
         --------------

         procedure Do_Merge (S : Symbol) is
         begin
            if Base_Is_Any
              or else ((Base_Any.Namespaces = No_Symbol
                        or else Get (Tmp, S) /= No_Symbol)
                       and then Get (No_Namespaces, S) = No_Symbol)
            then
               Set (Namespaces, S);
            end if;
         end Do_Merge;

         --------------
         -- Callback --
         --------------

         procedure Callback (Str : Byte_Sequence) is
         begin
            if Str = "##targetNamespace" then
               Do_Merge (Target_NS);

            elsif Str = "##other" then
               if Target_NS /= No_Symbol then
                  Set (No_Namespaces, Target_NS);
               end if;

               Set (No_Namespaces, Find (Symbols, "##local"));

            else
               Do_Merge (Find (Symbols, Str));  --  including ##any, ##local
            end if;
         end Callback;

         procedure All_Add is new For_Each_Item (Callback);
      begin
         if Sym /= No_Symbol then
            All_Add (Get (Sym).all);
         end if;
      end Merge;

      ------------------
      -- Add_To_Table --
      ------------------

      procedure Add_To_Table
        (Sym : Symbol; Table : in out Symbol_Htable.HTable)
      is
         procedure Callback (Str : Byte_Sequence);

         --------------
         -- Callback --
         --------------

         procedure Callback (Str : Byte_Sequence) is
         begin
            Set (Table, Find (Symbols, Str));
         end Callback;

         procedure All_Add is new For_Each_Item (Callback);
      begin
         if Sym /= No_Symbol then
            All_Add (Get (Sym).all);
         end if;
      end Add_To_Table;

      ---------------
      -- To_Symbol --
      ---------------

      function To_Symbol (Table : Symbol_Htable.HTable) return Symbol is
         Str  : Unbounded_String;
         S    : Symbol;
         Iter : Iterator := Symbol_Htable.First (Table);
      begin
         if Iter = No_Iterator then
            return No_Symbol;
         end if;

         while Iter /= No_Iterator loop
            S := Current (Iter);

            if Str = Null_Unbounded_String then
               Append (Str, Get (S).all);
            else
               Append (Str, " " & Get (S).all);
            end if;

            Symbol_Htable.Next (Table, Iter);
         end loop;

         return Find (Get (Grammar).Symbols, To_String (Str));
      end To_Symbol;

      --------------
      -- Callback --
      --------------

      procedure Callback (Str : Byte_Sequence) is
      begin
         if Str = "##targetNamespace" then
            if Target_NS = Empty_String then
               Set (Namespaces, Find (Symbols, "##local"));
            else
               Set (Namespaces, Target_NS);
            end if;

         elsif Str = "##other" then
            if Target_NS /= No_Symbol then
               Set (No_Namespaces, Target_NS);
            end if;

            Set (No_Namespaces, Find (Symbols, "##local"));
         else
            Set (Namespaces, Find (Symbols, Str)); --  including ##any, ##local
         end if;
      end Callback;

      procedure All_Items is new For_Each_Item (Callback);

   begin
      if Base_Any = No_Any_Descr then
         if Local /= No_Symbol then
            All_Items (Get (Local).all);
         end if;

         declare
            Result : constant Any_Descr := Any_Descr'
              (Process_Contents => Local_Process,
               No_Namespaces    => To_Symbol (No_Namespaces),
               Namespaces       => To_Symbol (Namespaces));
         begin
            Reset (Namespaces);
            Reset (No_Namespaces);
            Reset (Tmp);
            return Result;
         end;
      end if;

      Local_Is_Any := Local /= No_Symbol and then Get (Local).all = "##any";
      Base_Is_Any := Base_Any.Namespaces /= No_Symbol
        and then Get (Base_Any.Namespaces).all = "##any";

      if As_Restriction then
         --  The list of "Namespaces" is the intersection of the two (and
         --  empty if local is empty)
         --  From this, remove the list of the base's "No_Namespaces".
         --  We preserve those "No_Namespaces" into the new type, though.

         Add_To_Table (Base_Any.No_Namespaces, No_Namespaces);

         if Local_Is_Any then
            if Base_Any.Namespaces /= No_Symbol then
               Add_To_Table (Base_Any.Namespaces, Namespaces);
            elsif Local /= No_Symbol then
               Add_To_Table (Local, Namespaces);
            end if;
         else
            Add_To_Table (Base_Any.Namespaces, Tmp);
            Merge (Local);
         end if;

      else
         --  If the base type or the extension contains ##any, we will still
         --  accept any namespace

         if Base_Is_Any then
            Add_To_Table (Base_Any.Namespaces, Namespaces); --  ##any

         elsif Local_Is_Any then
            if Base_Any.No_Namespaces /= No_Symbol then
               Add_To_Table (Base_Any.No_Namespaces, No_Namespaces);
            elsif Local /= No_Symbol then
               Add_To_Table (Local, Namespaces);  --  ##any
            end if;

         else
            --  None of the two is ##any, so we just combine them. Since we
            --  have an extension, the attributes will have to match any of
            --  the namespaces.

            Add_To_Table (Base_Any.Namespaces, Namespaces);
            Add_To_Table (Base_Any.No_Namespaces, No_Namespaces);

            if Local /= No_Symbol then
               All_Items (Get (Local).all);
            end if;
         end if;
      end if;

      Result.Process_Contents := Local_Process;
      Result.Namespaces       := To_Symbol (Namespaces);
      Result.No_Namespaces    := To_Symbol (No_Namespaces);

      --  ??? If .Namespaces contain one common NS with .No_Namespaces, then
      --  we really have a ##any

      if Debug then
         if Local /= No_Symbol then
            Debug_Output ("Combine " & To_String (Base_Any)
                          & " and {" & Local_Process'Img
                          & " " & Get (Local).all & " target="
                          & Get (Target_NS).all & "} restr="
                          & As_Restriction'Img & " => "
                          & To_String (Result));
         else
            Debug_Output ("Combine " & To_String (Base_Any)
                          & " and {" & Local_Process'Img & " target="
                          & Get (Target_NS).all & "} restr="
                          & As_Restriction'Img & " => "
                          & To_String (Result));
         end if;
      end if;

      Reset (Namespaces);
      Reset (No_Namespaces);
      Reset (Tmp);

      return Result;

   exception
      when others =>
         Reset (Namespaces);
         Reset (No_Namespaces);
         Reset (Tmp);
         raise;
   end Combine;

   ---------------
   -- Match_Any --
   ---------------

   function Match_Any
     (Any : Any_Descr; Name : Qualified_Name) return Boolean
   is
      Matches       : Boolean := False;
      Invalid_No_NS : Boolean := False;

      procedure Callback (Str : Byte_Sequence);
      procedure Negate_Callback (Str : Byte_Sequence);

      ---------------------
      -- Negate_Callback --
      ---------------------

      procedure Negate_Callback (Str : Byte_Sequence) is
      begin
         if Str = "##local" then
            Invalid_No_NS := Invalid_No_NS or else Name.NS = Empty_String;
         else
            Invalid_No_NS := Invalid_No_NS or else Get (Name.NS).all = Str;
         end if;
      end Negate_Callback;

      --------------
      -- Callback --
      --------------

      procedure Callback (Str : Byte_Sequence) is
      begin
         if Matches then
            null;
         elsif Str = "##local" then
            Matches := Name.NS = Empty_String;
         else
            Matches := Get (Name.NS).all = Str;
         end if;
      end Callback;

      procedure All_Items is new For_Each_Item (Callback);
      procedure Negate_All_Items is new For_Each_Item (Negate_Callback);

   begin
      if Debug then
         Debug_Output
           ("match <any>: " & To_String (Any) & " and " & To_QName (Name));
      end if;

      if Any.Namespaces /= No_Symbol
        and then Get (Any.Namespaces).all = "##any"
      then
         return True;
      end if;

      if Any.Namespaces /= No_Symbol then
         All_Items (Get (Any.Namespaces).all);
      end if;

      if Any.No_Namespaces /= No_Symbol then
         Negate_All_Items (Get (Any.No_Namespaces).all);
      end if;

      if Debug then
         Debug_Output ("Matches=" & Matches'Img
                       & " Invalid_No_NS=" & Invalid_No_NS'Img);
      end if;

      if Any.Namespaces /= No_Symbol
        and then Any.No_Namespaces /= No_Symbol
      then
         return Matches or else not Invalid_No_NS;
      elsif Any.Namespaces /= No_Symbol then
         return Matches;
      elsif Any.No_Namespaces /= No_Symbol then
         return not Invalid_No_NS;
      else
         return False;
      end if;
   end Match_Any;

   -------------------------
   -- Validate_Attributes --
   -------------------------

   procedure Validate_Attributes
     (NFA    : access Schema_NFA'Class;
      Typ    : access Type_Descr;
      Reader : access Abstract_Validation_Reader'Class;
      Atts   : access Sax.Readers.Sax_Attribute_List;
      Is_Nil : in out Integer)
   is
      Length      : constant Natural := Get_Length (Atts.all);
      Valid_Attrs : Attribute_Validator_Array :=
                      To_Attribute_Array (NFA, Typ.Attributes);

      type Attr_Status is record
         Prohibited : Boolean := False;
         --  Prohibited explicitly, but it might be allowed through
         --  <anyAttribute>

         Seen  : Boolean := False;
      end record;
      Seen : array (1 .. Length) of Attr_Status := (others => (False, False));

      function Find_Attribute (Attr : Attribute_Descr) return Integer;
      --  Chech whether Named appears in Atts

      procedure Check_Named_Attribute (Index : Attribute_Validator_Index);
      --  Check a named attribute or a wildcard attribute

      procedure Check_Single_ID;
      --  If using XSD 1.0, check that there is a single ID attribute.
      --  This relies on the Sax.Attributes.Get_Type being set correctly.
      --  XSD 1.0 prevents having two such attributes, for easier conversion
      --  to DTD (see G.1.7 ID, IDREF, and related types)

      ---------------------
      -- Check_Single_ID --
      ---------------------

      procedure Check_Single_ID is
         Seen_ID : Boolean := False;
      begin
         for A in 1 .. Length loop
            if Get_Type (Atts.all, A) = Sax.Attributes.Id then
               if Seen_ID then
                  Validation_Error
                    (Reader,
                     "Elements can have a single ID attribute in XSD 1.0");
               end if;

               Seen_ID := True;
            end if;
         end loop;
      end Check_Single_ID;

      ---------------------------
      -- Check_Named_Attribute --
      ---------------------------

      procedure Check_Named_Attribute (Index : Attribute_Validator_Index) is
         Found  : Integer;
         Attr   : Attribute_Descr
           renames NFA.Attributes.Table (Valid_Attrs (Index).Validator);
      begin
         if not Valid_Attrs (Index).Visited then
            if Debug then
               Debug_Output
                 ("Checking attribute: "
                  & To_QName
                    (NFA.Attributes.Table
                       (Valid_Attrs (Index).Validator).Name)
                  & " " & Attr.Use_Type'Img & " " & Attr.Form'Img);
            end if;

            Valid_Attrs (Index).Visited := True;
            Found := Find_Attribute (Attr);

            if Found = -1 then
               case Attr.Use_Type is
                  when Required =>
                     Validation_Error
                       (Reader, "Attribute """
                        & To_QName (Attr.Name)
                        & """ is required in this context");
                  when Prohibited =>
                     null;
                  when Optional | Default =>
                     if Attr.Default /= No_Symbol then
                        Append
                           (List       => Atts.all,
                            Local_Name => Attr.Name.Local,
                            Prefix     => No_Symbol,
                            URI        => Attr.Name.NS,
                            Value      => Attr.Default,
                            Location   => Sax.Locators.No_Location);
                     end if;
               end case;

            else
               Seen (Found).Seen := True;

               case Attr.Form is
                  when Qualified =>
                     if Attr.Is_Local
                       and then Get_Prefix (Atts.all, Found) = Empty_String
                     then
                        Validation_Error
                          (Reader, "Attribute " & Get_Qname (Atts.all, Found)
                           & " must have a namespace");
                     end if;

                  when Unqualified =>
                     if Attr.Is_Local
                       and then Get_Prefix (Atts.all, Found) /= Empty_String
                     then
                        Validation_Error
                          (Reader, "Attribute " & Get_Qname (Atts.all, Found)
                           & " must not have a namespace");
                     end if;
               end case;

               case Attr.Use_Type is
                  when Prohibited =>
                     if Debug then
                        Debug_Output
                          ("Marking as prohibited, might be accepted by"
                           & " <anyAttribute>");
                     end if;
                     Seen (Found) := (Seen       => False,
                                      Prohibited => True);

                  when Optional | Required | Default =>
                     --  We do not need to check id here, since that is
                     --  automatically checked from Validate_Characters for the
                     --  attribute
                     Validate_Attribute (Attr, Reader, Atts, Found);
               end case;
            end if;
         end if;
      end Check_Named_Attribute;

      --------------------
      -- Find_Attribute --
      --------------------

      function Find_Attribute (Attr : Attribute_Descr) return Integer is
         Is_Local : constant Boolean := Attr.Is_Local;
         Matches  : Boolean;
      begin
         for A in 1 .. Length loop
            if not Seen (A).Seen
              and then Get_Name (Atts.all, A).Local = Attr.Name.Local
            then
               Matches :=
                  (Is_Local and Get_Prefix (Atts.all, A) = Empty_String)
                   or else Get_Name (Atts.all, A).NS = Attr.Name.NS;

               if Matches then
                  if Debug then
                     Debug_Output ("Found attribute: "
                                   & To_QName (Get_Name (Atts.all, A))
                                   & " prefix="
                                   & Get (Get_Prefix (Atts.all, A)).all
                                   & " at index" & A'Img
                                   & " Is_Local=" & Is_Local'Img
                                   & " Form=" & Attr.Form'Img);
                  end if;
                  return A;
               end if;
            end if;
         end loop;
         return -1;
      end Find_Attribute;

   begin
      --  All the xsi:* attributes should be valid, whatever the schema

      for S in Seen'Range loop
         if Get_Name (Atts.all, S).NS = Reader.XML_Instance_URI then
            if Get_Name (Atts.all, S).Local = Reader.Nil then
               Is_Nil := S;
               Seen (S).Seen := True;

               --  Following attributes are always valid
               --  See "Element Locally Valid (Complex Type)" 3.4.4.2
            elsif Get_Name (Atts.all, S).Local = Reader.Typ
              or else Get_Name (Atts.all, S).Local = Reader.Schema_Location
              or else Get_Name (Atts.all, S).Local =
                        Reader.No_Namespace_Schema_Location
            then
               Seen (S).Seen := True;
            end if;
         end if;
      end loop;

      for L in Valid_Attrs'Range loop
         Check_Named_Attribute (L);
      end loop;

      declare
         TRef : Global_Reference;
      begin
         for S in Seen'Range loop
            if not Seen (S).Seen then
               Seen (S).Seen := Match_Any
                 (Typ.Attributes.Any, Get_Name (Atts.all, S));

               if not Seen (S).Seen then
                  if Seen (S).Prohibited then
                     Validation_Error
                       (Reader, "Attribute """ & Get_Qname (Atts.all, S)
                        & """ is prohibited in this context "
                          & To_QName (Typ.Name));
                  elsif Typ.Attributes.Any = No_Any_Descr then
                     Validation_Error
                       (Reader, "Attribute """ & Get_Qname (Atts.all, S)
                        & """ invalid for type " & To_QName (Typ.Name));
                  else
                     Validation_Error
                       (Reader, "Attribute """ & Get_Qname (Atts.all, S)
                        & """ does not match attribute wildcard");
                  end if;
               end if;

               --  If the processing content forces it, we must check that
               --  there is indeed a valid definition for this attribute.

               case Typ.Attributes.Any.Process_Contents is
                  when Process_Skip =>
                     null;  --  Always OK
                     TRef := No_Global_Reference;

                  when Process_Lax =>
                     TRef := Reference_HTables.Get
                       (NFA.References.all,
                        (Get_Name (Atts.all, S), Ref_Attribute));

                  when Process_Strict =>
                     TRef := Reference_HTables.Get
                       (NFA.References.all,
                        (Get_Name (Atts.all, S), Ref_Attribute));
                     if TRef = No_Global_Reference then
                        Validation_Error
                          (Reader, "No definition found for """
                           & Get_Qname (Atts.all, S) & """");
                     end if;
               end case;

               if TRef /= No_Global_Reference then
                  Validate_Attribute
                    (NFA.Attributes.Table (TRef.Attributes.Named),
                     Reader, Atts, S);
               end if;

               Seen (S).Prohibited := False;
            end if;
         end loop;
      end;

      Check_Single_ID;
   end Validate_Attributes;

   -----------------------
   -- To_Graphic_String --
   -----------------------

   function To_Graphic_String (Str : Byte_Sequence) return String is
      To_Hex : constant array (0 .. 15) of Character :=
                 ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                  'A', 'B', 'C', 'D', 'E', 'F');
      Result : String (1 .. 4 * Str'Length);
      Index  : Integer := Result'First;
   begin
      for S in Str'Range loop
         if Character'Pos (Str (S)) >= 32
           and then Character'Pos (Str (S)) <= 128
           and then Is_Graphic (Str (S))
         then
            Result (Index) := Str (S);
            Index := Index + 1;
         else
            Result (Index) := '[';
            Result (Index + 1) := To_Hex (Character'Pos (Str (S)) / 16);
            Result (Index + 2) := To_Hex (Character'Pos (Str (S)) mod 16);
            Result (Index + 3) := ']';
            Index := Index + 4;
         end if;
      end loop;
      return Result (1 .. Index - 1);
   end To_Graphic_String;

   ------------------------
   -- Validate_Attribute --
   ------------------------

   procedure Validate_Attribute
     (Attr   : Attribute_Descr;
      Reader : access Abstract_Validation_Reader'Class;
      Atts   : access Sax_Attribute_List;
      Index  : Natural)
   is
      Value    : Symbol := Get_Value (Atts.all, Index);
      Val      : Cst_Byte_Sequence_Access;
      Is_Equal : Boolean;
      Descr    : Simple_Type_Descr;
   begin
      if Debug then
         Debug_Output ("Validate attribute " & To_QName (Attr.Name)
                       & " simpleType=" & Attr.Simple_Type'Img);
      end if;

      if Attr.Simple_Type = No_Simple_Type_Index then
         if Debug then
            Debug_Output ("No simple type defined");
         end if;
      else
         Descr := Get_Simple_Type (Get (Reader.Grammar).NFA, Attr.Simple_Type);
         Normalize_And_Validate
           (Parser => Reader,
            Simple => Attr.Simple_Type,
            Fixed  => Value,
            Loc    => Get_Location (Atts.all, Index));
         Set_Value (Atts.all, Index, Value);

         if Descr.Kind = Primitive_ID then
            Set_Type (Atts.all, Index, Sax.Attributes.Id);
         end if;
      end if;

      Val := Get (Value);

      --  3.2.4 [Attribute Declaration Value] indicates we should check Fixed
      --  with the "actual value" of the attribute, not the "normalized value".
      --  However, we need to match depending on the type of the attribute: if
      --  it is an integer, the whitespaces are irrelevant; likewise for a list

      if Attr.Fixed /= No_Symbol then
         if Debug then
            Debug_Output ("Attribute value must be equal to """
                          & Get (Attr.Fixed).all & """");
         end if;

         if Attr.Simple_Type = No_Simple_Type_Index then
            Is_Equal := Get (Attr.Fixed).all = Val.all;
         else
            Is_Equal := Equal (Reader, Attr.Simple_Type, Attr.Fixed, Val.all);
         end if;

         if not Is_Equal then
            Validation_Error
              (Reader, "value must be """
               & To_Graphic_String (Get (Attr.Fixed).all)
               & """ (found """ & To_Graphic_String (Val.all) & """)",
               Get_Location (Atts.all, Index));
         end if;
      end if;
   end Validate_Attribute;

   --------------
   -- To_QName --
   --------------

   function To_QName
     (Name : Qualified_Name) return Unicode.CES.Byte_Sequence is
   begin
      if Name = No_Qualified_Name then
         return "";
      else
         return Sax.Readers.To_QName (Name.NS, Name.Local);
      end if;
   end To_QName;

   ----------------------
   -- Get_Symbol_Table --
   ----------------------

   function Get_Symbol_Table
     (Grammar : XML_Grammar) return Sax.Utils.Symbol_Table is
   begin
      if Grammar = No_Grammar then
         return Symbol_Table_Pointers.Null_Pointer;
      else
         return Get (Grammar).Symbols;
      end if;
   end Get_Symbol_Table;

   ----------------------
   -- Set_Symbol_Table --
   ----------------------

   procedure Set_Symbol_Table
     (Grammar : XML_Grammar; Symbols : Sax.Utils.Symbol_Table) is
   begin
      if Grammar /= No_Grammar then
         Get (Grammar).Symbols := Symbols;
      end if;
   end Set_Symbol_Table;

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

   procedure Free (List : in out String_List) is
      procedure Unchecked_Free is new Ada.Unchecked_Deallocation
        (String_List_Record, String_List);
      L : String_List;
   begin
      while List /= null loop
         L := List.Next;
         Unchecked_Free (List);
         List := L;
      end loop;
   end Free;

   ------------------------------
   -- Create_Grammar_If_Needed --
   ------------------------------

   procedure Create_Grammar_If_Needed
     (Grammar : in out XML_Grammar;
      Symbols : Symbol_Table := No_Symbol_Table)
   is
      use Types_Tables;
      G : XML_Grammars.Encapsulated_Access;
   begin
      if Grammar = No_Grammar then
         G     := new XML_Grammar_Record;
         G.Symbols := Symbols;
         G.NFA := new Schema_NFA;
         G.NFA.Initialize (States_Are_Statefull => True);
         Init (G.NFA.Attributes);
         Init (G.NFA.Enumerations);
         Init (G.NFA.Types);
         G.NFA.References :=
           new Reference_HTables.HTable (Size => Reference_HTable_Size);
         Simple_Type_Tables.Init (G.NFA.Simple_Types);
         Grammar  := Allocate (G);
      end if;
   end Create_Grammar_If_Needed;

   ---------------------
   -- Set_XSD_Version --
   ---------------------

   procedure Set_XSD_Version
     (Grammar     : in out XML_Grammar;
      XSD_Version : XSD_Versions) is
   begin
      Create_Grammar_If_Needed (Grammar);
      GNAT.Task_Lock.Lock;
      Get (Grammar).XSD_Version := XSD_Version;
      GNAT.Task_Lock.Unlock;
   end Set_XSD_Version;

   ---------------------
   -- Get_XSD_Version --
   ---------------------

   function Get_XSD_Version (Grammar : XML_Grammar) return XSD_Versions is
      G   : XML_Grammars.Encapsulated_Access;
   begin
      G := Get (Grammar);
      if G = null then
         return XSD_1_1;
      else
         return G.XSD_Version;
      end if;
   end Get_XSD_Version;

   -----------------------------
   -- Create_Global_Attribute --
   -----------------------------

   procedure Create_Global_Attribute
     (Parser    : access Abstract_Validation_Reader'Class;
      Attr      : Attribute_Descr;
      Loc       : Sax.Locators.Location)
   is
      use Reference_HTables;
      NFA : constant Schema_NFA_Access := Get_NFA (Parser.Grammar);
      List : Attributes_List := No_Attributes;
   begin
      Add_Attribute (Parser, List, Attr, Loc => Loc);
      Set
        (NFA.References.all,
         (Kind => Ref_Attribute, Name => Attr.Name, Attributes => List));
   end Create_Global_Attribute;

   ------------------------
   -- Create_Simple_Type --
   ------------------------

   function Create_Simple_Type
     (NFA   : access Schema_NFA'Class;
      Descr : Schema.Simple_Types.Simple_Type_Descr)
      return Schema.Simple_Types.Simple_Type_Index
   is
      use Simple_Type_Tables;
   begin
      Append (NFA.Simple_Types, Descr);
      return Last (NFA.Simple_Types);
   end Create_Simple_Type;

   -----------------
   -- Create_Type --
   -----------------

   function Create_Type
     (NFA   : access Schema_NFA'Class;
      Descr : Type_Descr) return Type_Index
   is
      use Reference_HTables, Types_Tables;
   begin
      Append (NFA.Types, Descr);

      if Descr.Name /= No_Qualified_Name then
         if Debug then
            Debug_Output ("Create_global_type: " & To_QName (Descr.Name)
                          & " at index" & Last (NFA.Types)'Img);
         end if;
         Set (NFA.References.all, (Ref_Type, Descr.Name, Last (NFA.Types)));
      end if;

      return Last (NFA.Types);
   end Create_Type;

   ---------------------
   -- Get_Simple_Type --
   ---------------------

   function Get_Simple_Type
     (NFA    : access Schema_NFA'Class;
      Simple  : Schema.Simple_Types.Simple_Type_Index)
      return Schema.Simple_Types.Simple_Type_Descr is
   begin
      return NFA.Simple_Types.Table (Simple);
   end Get_Simple_Type;

   --------------------
   -- Get_Type_Descr --
   --------------------

   function Get_Type_Descr
     (NFA   : access Schema_NFA'Class;
      Index : Type_Index) return access Type_Descr is
   begin
      return NFA.Types.Table (Index)'Unrestricted_Access;
   end Get_Type_Descr;

   -------------------
   -- Simple_Nested --
   -------------------

   function Simple_Nested
     (NFA : access Schema_NFA'Class) return Schema_State_Machines.State is
   begin
      return NFA.Simple_Nested;
   end Simple_Nested;

   ------------------------
   -- Initialize_Grammar --
   ------------------------

   procedure Initialize_Grammar
     (Reader : in out Abstract_Validation_Reader'Class)
   is
      use Reference_HTables;
      G : XML_Grammars.Encapsulated_Access;

      function Register
        (Local          : Byte_Sequence;
         Descr          : Simple_Type_Descr;
         Restriction_Of : Type_Index) return Type_Index;

      function Create_UR_Type
        (Process_Contents : Process_Contents_Type)
         return State;
      --  Return the start state for a nested NFA for ur-type
      --  All children (at any depth level) are allowed.
      --  Any character contents is allowed.

      --------------
      -- Register --
      --------------

      function Register
        (Local          : Byte_Sequence;
         Descr          : Simple_Type_Descr;
         Restriction_Of : Type_Index) return Type_Index
      is
         Simple : Simple_Type_Index;
      begin
         Simple := Create_Simple_Type (G.NFA, Descr);
         return Create_Type
           (G.NFA,
            Type_Descr'
              (Name            =>
                 (NS    => Reader.XML_Schema_URI,
                  Local => Find (G.Symbols, Local)),
               Attributes      => No_Attributes,
               Block           => No_Block,
               Final           => (others => False),
               Restriction_Of  => Restriction_Of,
               Extension_Of    => No_Type_Index,
               Simple_Content  => Simple,
               Mixed           => False,
               Is_Abstract     => False,
               Complex_Content => No_State));
      end Register;

      --------------------
      -- Create_UR_Type --
      --------------------

      function Create_UR_Type
        (Process_Contents : Process_Contents_Type)
         return State
      is
         S1      : constant State := G.NFA.Add_State;
         Ur_Type : constant Nested_NFA := G.NFA.Create_Nested (S1);
         S2, S3  : State;
         List    : Attributes_List := No_Attributes;
         Index   : Type_Index;

      begin
         List.Any := Any_Descr'
           (Process_Contents => Process_Lax,
            No_Namespaces    => No_Symbol,
            Namespaces       => Reader.Any_Namespace);
         Index := Create_Type
           (G.NFA,
            Type_Descr'
              (Name            => (NS    => Reader.XML_Schema_URI,
                                   Local => Reader.Ur_Type),
               Attributes      => List,
               Mixed           => True,
               Complex_Content => S1,
               others          => <>));

         G.NFA.Set_Data (S1, (Simple   => Index,
                              Fixed    => No_Symbol,
                              Default  => No_Symbol,
                              Nillable => True,
                              Block    => No_Block));
         S2 := G.NFA.Add_State ((Simple   => Index,
                                 Fixed    => No_Symbol,
                                 Default  => No_Symbol,
                                 Nillable => True,
                                 Block    => No_Block));

         G.NFA.Set_Nested (S2, Ur_Type);

         pragma Assert (Reader.Any_Namespace /= No_Symbol);
         G.NFA.Add_Transition
           (S1, S2,
            (Kind => Transition_Any,
             Any  => (Process_Contents => Process_Contents,
                      Namespaces       => Reader.Any_Namespace,
                      No_Namespaces    => No_Symbol)));

         S3 := G.NFA.Add_State;
         G.NFA.On_Empty_Nested_Exit (S2, S3);
         G.NFA.Add_Empty_Transition (S3, S1); --  maxOccurs="unbounded"
         G.NFA.Add_Empty_Transition (S1, S3); --  minOccurs="0"
         G.NFA.Add_Transition (S3, Final_State, (Kind => Transition_Close));
         return S1;
      end Create_UR_Type;

      procedure Do_Register is new Register_Predefined_Types
        (Type_Index, No_Type_Index, Register);

      Attr : Attribute_Descr;
   begin
      Create_Grammar_If_Needed (Reader.Grammar, Get_Symbol_Table (Reader));

      --  In the case of a shared grammar, created will always be false (since
      --  it has already been parsed), so the code below will not be called.
      --  As such, it is safe to let it modify the grammar.

      G := Get (Reader.Grammar);

      if Get (G.NFA.References.all,
        (Name => (NS => Reader.XML_Schema_URI,
                  Local => Reader.S_Boolean),
         Kind => Ref_Type)) = No_Global_Reference
      then
         Do_Register (G.Symbols);   --  Simple types

         Attr := (Name => (NS => Reader.XML_URI, Local => Reader.Lang),
                  Form => Qualified,
                  others => <>);
         Create_Global_Attribute (Reader'Access, Attr, No_Location);

         Attr := (Name =>
                    (NS => Reader.XML_URI, Local => Find (G.Symbols, "space")),
                  Form => Qualified,
                  others => <>);
         Create_Global_Attribute (Reader'Access, Attr, No_Location);

         Attr := (Name => (NS => Reader.XML_URI, Local => Reader.Base),
                  Form => Qualified,
                  others => <>);
         Create_Global_Attribute (Reader'Access, Attr, No_Location);

         --  Added support for <ur-Type>

         G.NFA.Ur_Type := Create_UR_Type (Process_Lax);
         G.NFA.Ur_Type_Skip := Create_UR_Type (Process_Skip);

         Add_Schema_For_Schema (Reader);

         --  The simple nested NFA

         G.NFA.Simple_Nested := G.NFA.Add_State;
         G.NFA.Add_Transition
           (G.NFA.Simple_Nested, Final_State,
            (Kind => Transition_Close));

         --  Save the current state, so that we can restore the grammar to just
         --  this metaschema.

         G.NFA.Metaschema_NFA_Last := Get_Snapshot (G.NFA);
         G.NFA.Metaschema_Simple_Types_Last :=
           Simple_Type_Tables.Last (G.NFA.Simple_Types);
         G.NFA.Metaschema_Attributes_Last :=
           Attributes_Tables.Last (G.NFA.Attributes);
         G.NFA.Metaschema_Enumerations_Last :=
           Enumeration_Tables.Last (G.NFA.Enumerations);
         G.NFA.Metaschema_Types_Last := Types_Tables.Last (G.NFA.Types);
      end if;
   end Initialize_Grammar;

   -------------
   -- Ur_Type --
   -------------

   function Ur_Type
     (NFA              : access Schema_NFA'Class;
      Process_Contents : Process_Contents_Type)
      return Schema_State_Machines.Nested_NFA
   is
   begin
      case Process_Contents is
         when Process_Skip =>
            return NFA.Create_Nested (NFA.Ur_Type_Skip);
         when others =>
            return NFA.Create_Nested (NFA.Ur_Type);
      end case;
   end Ur_Type;

   ----------------
   -- Debug_Dump --
   ----------------

   procedure Debug_Dump (Grammar : XML_Grammar) is
      Str : String_List;
      G    : constant XML_Grammars.Encapsulated_Access := Get (Grammar);
   begin
      if Debug then
         Str := G.Parsed_Locations;
         while Str /= null loop
            Debug_Output ("   Parsed location: " & Get (Str.Str).all);
            Str := Str.Next;
         end loop;
      end if;
   end Debug_Dump;

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

   procedure Free (Grammar : in out XML_Grammar_Record) is
      procedure Unchecked_Free is new Ada.Unchecked_Deallocation
        (Reference_HTables.HTable, Reference_HTable);
   begin
      if Debug then
         Debug_Output ("Freeing grammar");
      end if;

      Reset_Simple_Types (Grammar.NFA, No_Simple_Type_Index);
      Enumeration_Tables.Free (Grammar.NFA.Enumerations);
      Free (Grammar.NFA.Attributes);
      Reference_HTables.Reset (Grammar.NFA.References.all);
      Unchecked_Free (Grammar.NFA.References);
      Types_Tables.Free (Grammar.NFA.Types);
      Symbol_Htable.Reset (Grammar.NFA.Notations);
      Free (NFA_Access (Grammar.NFA));
      Free (Grammar.Parsed_Locations);
   end Free;

   ------------------------
   -- Reset_Simple_Types --
   ------------------------

   procedure Reset_Simple_Types
     (NFA : access Schema_NFA'Class;
      To  : Simple_Type_Index := No_Simple_Type_Index) is
   begin
      for S in To + 1 .. Simple_Type_Tables.Last (NFA.Simple_Types) loop
         Free (NFA.Simple_Types.Table (S).Pattern);
      end loop;

      if To = No_Simple_Type_Index then
         Simple_Type_Tables.Free (NFA.Simple_Types);
      else
         Simple_Type_Tables.Set_Last (NFA.Simple_Types, To);
      end if;
   end Reset_Simple_Types;

   -----------
   -- Reset --
   -----------

   procedure Reset (Grammar : in out XML_Grammar) is
      G   : constant XML_Grammars.Encapsulated_Access := Get (Grammar);
      NFA : Schema_NFA_Access;

      function Preserve (TRef : Global_Reference) return Boolean;

      --------------
      -- Preserve --
      --------------

      function Preserve (TRef : Global_Reference) return Boolean is
         R : Boolean;
      begin
         case TRef.Kind is
            when Ref_Element =>
               R := Exists (NFA.Metaschema_NFA_Last, TRef.Element);
            when Ref_Type =>
               R := TRef.Typ <= NFA.Metaschema_Types_Last;
            when Ref_Group =>
               R := Exists (NFA.Metaschema_NFA_Last, TRef.Gr_Start);
            when Ref_Attribute | Ref_AttrGroup =>
               R := TRef.Attributes.Named <= NFA.Metaschema_Attributes_Last;
         end case;
         return R;
      end Preserve;

      procedure Remove_All is new Reference_HTables.Remove_All (Preserve);

   begin
      if Debug then
         Debug_Output ("Partial reset of the grammar");
      end if;

      if G = null then
         return;
      end if;

      NFA := G.NFA;
      Free (G.Parsed_Locations);

      if NFA.Metaschema_NFA_Last /= No_NFA_Snapshot then
         if Debug then
            Debug_Output ("Preserve metaschema information");
         end if;
         Enumeration_Tables.Set_Last
           (NFA.Enumerations, NFA.Metaschema_Enumerations_Last);
         Attributes_Tables.Set_Last
           (NFA.Attributes, NFA.Metaschema_Attributes_Last);
         Types_Tables.Set_Last (NFA.Types, NFA.Metaschema_Types_Last);

         Reset_Simple_Types (NFA, NFA.Metaschema_Simple_Types_Last);
         Remove_All (NFA.References.all);

         --  From the toplevel choice (ie the list of valid global elements),
         --  we need to keep only those belonging to our metaschema, not those
         --  from grammars loaded afterward

         Reset_To_Snapshot (NFA, NFA.Metaschema_NFA_Last);
      end if;
   end Reset;

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

   function Get_Key (Ref : Global_Reference) return Reference_Name is
   begin
      return (Kind => Ref.Kind, Name => Ref.Name);
   end Get_Key;

   --------------------
   -- URI_Was_Parsed --
   --------------------

   function URI_Was_Parsed
     (Grammar : XML_Grammar; URI : Symbol) return Boolean
   is
      L : String_List;
   begin
      if Grammar /= No_Grammar then
         L := Get (Grammar).Parsed_Locations;
         while L /= null loop
            if Debug then
               Debug_Output ("URI_Was_Parsed ("
                             & Get (URI).all & ") ? Compare with "
                             & Get (L.Str).all);
            end if;
            if L.Str = URI then
               if Debug then
                  Debug_Output ("    => Yes, already parsed");
               end if;
               return True;
            end if;
            L := L.Next;
         end loop;
      end if;
      return False;
   end URI_Was_Parsed;

   --------------------
   -- Set_Parsed_URI --
   --------------------

   procedure Set_Parsed_URI
     (Reader : in out Abstract_Validation_Reader'Class;
      URI    : Symbol) is
   begin
      Initialize_Grammar (Reader);

      if Debug then
         Debug_Output ("Set_Parsed_UI: " & Get (URI).all);
      end if;
      Get (Reader.Grammar).Parsed_Locations := new String_List_Record'
        (Str  => URI,
         Next => Get (Reader.Grammar).Parsed_Locations);
   end Set_Parsed_URI;

   -------------
   -- Get_NFA --
   -------------

   function Get_NFA (Grammar : XML_Grammar) return Schema_NFA_Access is
   begin
      return Get (Grammar).NFA;
   end Get_NFA;

   --------------------
   -- Get_References --
   --------------------

   function Get_References (Grammar : XML_Grammar) return Reference_HTable is
   begin
      return Get (Grammar).NFA.References;
   end Get_References;

   ------------------------
   -- Initialize_Symbols --
   ------------------------

   overriding procedure Initialize_Symbols
     (Parser : in out Abstract_Validation_Reader)
   is
      use Symbol_Table_Pointers;
   begin
      Initialize_Symbols (Sax_Reader (Parser));

      if Parser.Grammar /= No_Grammar then
         if Get (Parser.Grammar).Symbols =
           Symbol_Table_Pointers.Null_Pointer
         then
            if Debug then
               Debug_Output ("Initialze_Symbols, set grammar's symbol table");
            end if;
            Get (Parser.Grammar).Symbols := Get_Symbol_Table (Parser);
         end if;
      end if;

      if Parser.Xmlns /= No_Symbol then
         return;
      end if;

      Parser.All_NNI                := Find_Symbol (Parser, "allNNI");
      Parser.Annotated              := Find_Symbol (Parser, "annotated");
      Parser.Annotation             := Find_Symbol (Parser, "annotation");
      Parser.Any                    := Find_Symbol (Parser, "any");
      Parser.Any_Attribute          := Find_Symbol (Parser, "anyAttribute");
      Parser.Any_Namespace          := Find_Symbol (Parser, "##any");
      Parser.Any_Simple_Type        := Find_Symbol (Parser, "anySimpleType");
      Parser.Anytype                := Find_Symbol (Parser, "anyType");
      Parser.Appinfo                := Find_Symbol (Parser, "appinfo");
      Parser.Attr_Decls             := Find_Symbol (Parser, "attrDecls");
      Parser.Attribute              := Find_Symbol (Parser, "attribute");
      Parser.Attribute_Group        := Find_Symbol (Parser, "attributeGroup");
      Parser.Attribute_Group_Ref  :=
        Find_Symbol (Parser, "attributeGroupRef");
      Parser.Base                   := Find_Symbol (Parser, "base");
      Parser.Block                  := Find_Symbol (Parser, "block");
      Parser.Block_Default          := Find_Symbol (Parser, "blockDefault");
      Parser.Block_Set              := Find_Symbol (Parser, "blockSet");
      Parser.Choice                 := Find_Symbol (Parser, "choice");
      Parser.Complex_Content        := Find_Symbol (Parser, "complexContent");
      Parser.Complex_Extension_Type :=
        Find_Symbol (Parser, "complexExtensionType");
      Parser.Complex_Restriction_Type :=
        Find_Symbol (Parser, "complexRestrictionType");
      Parser.Complex_Type           := Find_Symbol (Parser, "complexType");
      Parser.Complex_Type_Model     :=
        Find_Symbol (Parser, "complexTypeModel");
      Parser.Def_Ref                := Find_Symbol (Parser, "defRef");
      Parser.Default                := Find_Symbol (Parser, "default");
      Parser.Derivation_Control     :=
        Find_Symbol (Parser, "derivationControl");
      Parser.Derivation_Set         := Find_Symbol (Parser, "derivationSet");
      Parser.Documentation          := Find_Symbol (Parser, "documentation");
      Parser.Element                := Find_Symbol (Parser, "element");
      Parser.Enumeration            := Find_Symbol (Parser, "enumeration");
      Parser.Explicit_Group         := Find_Symbol (Parser, "explicitGroup");
      Parser.Extension              := Find_Symbol (Parser, "extension");
      Parser.Extension_Type         := Find_Symbol (Parser, "extensionType");
      Parser.Facet                  := Find_Symbol (Parser, "facet");
      Parser.Field                  := Find_Symbol (Parser, "field");
      Parser.Final                  := Find_Symbol (Parser, "final");
      Parser.Final_Default          := Find_Symbol (Parser, "finalDefault");
      Parser.Fixed                  := Find_Symbol (Parser, "fixed");
      Parser.Form                   := Find_Symbol (Parser, "form");
      Parser.Form_Choice            := Find_Symbol (Parser, "formChoice");
      Parser.Fraction_Digits        := Find_Symbol (Parser, "fractionDigits");
      Parser.Group                  := Find_Symbol (Parser, "group");
      Parser.Group_Def_Particle     :=
        Find_Symbol (Parser, "groupDefParticle");
      Parser.Group_Ref              := Find_Symbol (Parser, "groupRef");
      Parser.Id                     := Find_Symbol (Parser, "id");
      Parser.IDREF                  := Find_Symbol (Parser, "IDREF");
      Parser.IDREFS                 := Find_Symbol (Parser, "IDREFS");
      Parser.Identity_Constraint    :=
        Find_Symbol (Parser, "identityConstraint");
      Parser.Import                 := Find_Symbol (Parser, "import");
      Parser.Include                := Find_Symbol (Parser, "include");
      Parser.Item_Type              := Find_Symbol (Parser, "itemType");
      Parser.Key                    := Find_Symbol (Parser, "key");
      Parser.Keybase                := Find_Symbol (Parser, "keybase");
      Parser.Keyref                 := Find_Symbol (Parser, "keyref");
      Parser.Lang                   := Find_Symbol (Parser, "lang");
      Parser.Lax                    := Find_Symbol (Parser, "lax");
      Parser.Length                 := Find_Symbol (Parser, "length");
      Parser.List                   := Find_Symbol (Parser, "list");
      Parser.Local                  := Find_Symbol (Parser, "##local");
      Parser.Local_Complex_Type     :=
        Find_Symbol (Parser, "localComplexType");
      Parser.Local_Element          := Find_Symbol (Parser, "localElement");
      Parser.Local_Simple_Type      := Find_Symbol (Parser, "localSimpleType");
      Parser.MaxExclusive           := Find_Symbol (Parser, "maxExclusive");
      Parser.MaxInclusive           := Find_Symbol (Parser, "maxInclusive");
      Parser.MaxOccurs              := Find_Symbol (Parser, "maxOccurs");
      Parser.Max_Bound              := Find_Symbol (Parser, "maxBound");
      Parser.Maxlength              := Find_Symbol (Parser, "maxLength");
      Parser.Member_Types           := Find_Symbol (Parser, "memberTypes");
      Parser.MinExclusive           := Find_Symbol (Parser, "minExclusive");
      Parser.MinInclusive           := Find_Symbol (Parser, "minInclusive");
      Parser.MinOccurs              := Find_Symbol (Parser, "minOccurs");
      Parser.Min_Bound              := Find_Symbol (Parser, "minBound");
      Parser.Minlength              := Find_Symbol (Parser, "minLength");
      Parser.Mixed                  := Find_Symbol (Parser, "mixed");
      Parser.NCName                 := Find_Symbol (Parser, "NCName");
      Parser.NMTOKEN                := Find_Symbol (Parser, "NMTOKEN");
      Parser.Name                   := Find_Symbol (Parser, "name");
      Parser.Named_Attribute_Group  :=
        Find_Symbol (Parser, "namedAttributeGroup");
      Parser.Named_Group            := Find_Symbol (Parser, "namedGroup");
      Parser.Namespace              := Find_Symbol (Parser, "namespace");
      Parser.Namespace_List         := Find_Symbol (Parser, "namespaceList");
      Parser.Nested_Particle        := Find_Symbol (Parser, "nestedParticle");
      Parser.Nil                    := Find_Symbol (Parser, "nil");
      Parser.Nillable               := Find_Symbol (Parser, "nillable");
      Parser.No_Namespace_Schema_Location :=
        Find_Symbol (Parser, "noNamespaceSchemaLocation");
      Parser.Non_Negative_Integer   :=
        Find_Symbol (Parser, "nonNegativeInteger");
      Parser.Notation               := Find_Symbol (Parser, "notation");
      Parser.Num_Facet              := Find_Symbol (Parser, "numFacet");
      Parser.Occurs                 := Find_Symbol (Parser, "occurs");
      Parser.Open_Attrs             := Find_Symbol (Parser, "openAttrs");
      Parser.Optional               := Find_Symbol (Parser, "optional");
      Parser.Other_Namespace        := Find_Symbol (Parser, "##other");
      Parser.Particle               := Find_Symbol (Parser, "particle");
      Parser.Pattern                := Find_Symbol (Parser, "pattern");
      Parser.Positive_Integer       := Find_Symbol (Parser, "positiveInteger");
      Parser.Precision_Decimal      :=
        Find_Symbol (Parser, "precisionDecimal");
      Parser.Process_Contents       := Find_Symbol (Parser, "processContents");
      Parser.Prohibited             := Find_Symbol (Parser, "prohibited");
      Parser.Public                 := Find_Symbol (Parser, "public");
      Parser.QName                  := Find_Symbol (Parser, "QName");
      Parser.Qualified              := Find_Symbol (Parser, "qualified");
      Parser.Real_Group             := Find_Symbol (Parser, "realGroup");
      Parser.Redefinable            := Find_Symbol (Parser, "redefinable");
      Parser.Redefine               := Find_Symbol (Parser, "redefine");
      Parser.Reduced_Derivation_Control :=
        Find_Symbol (Parser, "reducedDerivationControl");
      Parser.Ref                    := Find_Symbol (Parser, "ref");
      Parser.Refer                  := Find_Symbol (Parser, "refer");
      Parser.Required               := Find_Symbol (Parser, "required");
      Parser.Restriction            := Find_Symbol (Parser, "restriction");
      Parser.Restriction_Type       := Find_Symbol (Parser, "restrictionType");
      Parser.S_1                    := Find_Symbol (Parser, "1");
      Parser.S_Abstract             := Find_Symbol (Parser, "abstract");
      Parser.S_All                  := Find_Symbol (Parser, "all");
      Parser.S_Attribute_Form_Default :=
        Find_Symbol (Parser, "attributeFormDefault");
      Parser.S_Boolean              := Find_Symbol (Parser, "boolean");
      Parser.S_Element_Form_Default :=
        Find_Symbol (Parser, "elementFormDefault");
      Parser.S_False                := Find_Symbol (Parser, "false");
      Parser.S_Schema               := Find_Symbol (Parser, "schema");
      Parser.S_String               := Find_Symbol (Parser, "string");
      Parser.S_Use                  := Find_Symbol (Parser, "use");
      Parser.Schema_Location        := Find_Symbol (Parser, "schemaLocation");
      Parser.Schema_Top             := Find_Symbol (Parser, "schemaTop");
      Parser.Selector               := Find_Symbol (Parser, "selector");
      Parser.Sequence               := Find_Symbol (Parser, "sequence");
      Parser.Simple_Content         := Find_Symbol (Parser, "simpleContent");
      Parser.Simple_Derivation      :=
        Find_Symbol (Parser, "simpleDerivation");
      Parser.Simple_Derivation_Set  :=
        Find_Symbol (Parser, "simpleDerivationSet");
      Parser.Simple_Extension_Type  :=
        Find_Symbol (Parser, "simpleExtensionType");
      Parser.Simple_Restriction_Model :=
        Find_Symbol (Parser, "simpleRestrictionModel");
      Parser.Simple_Restriction_Type  :=
        Find_Symbol (Parser, "simpleRestrictionType");
      Parser.Simple_Type            := Find_Symbol (Parser, "simpleType");
      Parser.Source                 := Find_Symbol (Parser, "source");
      Parser.Strict                 := Find_Symbol (Parser, "strict");
      Parser.Substitution_Group     :=
        Find_Symbol (Parser, "substitutionGroup");
      Parser.System                 := Find_Symbol (Parser, "system");
      Parser.Target_Namespace       :=
        Find_Symbol (Parser, "##targetNamespace");
      Parser.Namespace_Target       := Find_Symbol (Parser, "targetNamespace");
      Parser.Token                  := Find_Symbol (Parser, "token");
      Parser.Top_Level_Attribute    :=
        Find_Symbol (Parser, "topLevelAttribute");
      Parser.Top_Level_Complex_Type :=
        Find_Symbol (Parser, "topLevelComplexType");
      Parser.Top_Level_Element      :=
        Find_Symbol (Parser, "topLevelElement");
      Parser.Top_Level_Simple_Type  :=
        Find_Symbol (Parser, "topLevelSimpleType");
      Parser.Total_Digits           := Find_Symbol (Parser, "totalDigits");
      Parser.Typ                    := Find_Symbol (Parser, "type");
      Parser.Type_Def_Particle      := Find_Symbol (Parser, "typeDefParticle");
      Parser.UC_ID                  := Find_Symbol (Parser, "ID");
      Parser.URI_Reference          := Find_Symbol (Parser, "uriReference");
      Parser.Unbounded              := Find_Symbol (Parser, "unbounded");
      Parser.Union                  := Find_Symbol (Parser, "union");
      Parser.Unique                 := Find_Symbol (Parser, "unique");
      Parser.Unqualified            := Find_Symbol (Parser, "unqualified");
      Parser.Ur_Type                := Find_Symbol (Parser, "ur-Type");
      Parser.Value                  := Find_Symbol (Parser, "value");
      Parser.Version                := Find_Symbol (Parser, "version");
      Parser.Whitespace             := Find_Symbol (Parser, "whiteSpace");
      Parser.Wildcard               := Find_Symbol (Parser, "wildcard");
      Parser.XML_Instance_URI       := Find_Symbol (Parser, XML_Instance_URI);

      Parser.XML_Schema_URI         := Find_Symbol (Parser, XML_Schema_URI);
      Parser.XML_URI                := Find_Symbol (Parser, XML_URI);
      Parser.XPath                  := Find_Symbol (Parser, "xpath");
      Parser.XPath_Expr_Approx      := Find_Symbol (Parser, "XPathExprApprox");
      Parser.XPath_Spec             := Find_Symbol (Parser, "XPathSpec");
      Parser.Xmlns                  := Find_Symbol (Parser, "xmlns");
   end Initialize_Symbols;

   -----------
   -- Image --
   -----------

   function Image (Trans : Transition_Descr) return String is
   begin
      case Trans.Kind is
         when Transition_Symbol | Transition_Symbol_From_All =>
            if Trans.Name.Local = No_Symbol then
               return "";
            else
               return To_QName (Trans.Name);
            end if;
         when Transition_Close | Transition_Close_From_All =>
            return "close parent";
         when Transition_Any   => return "<any>";
      end case;
   end Image;

   -----------
   -- Image --
   -----------

   function Image
     (Self : access NFA'Class;
      S    : Schema_State_Machines.State;
      Data : State_Data) return String
   is
      pragma Unreferenced (S);
      Local : Symbol;
   begin
      if Data.Simple = No_Type_Index then
         return "";
      else
         Local :=
           Schema_NFA_Access (Self).Types.Table (Data.Simple).Name.Local;
         if Local = No_Symbol then
            return "";
         else
            return Get (Local).all;
         end if;
      end if;
   end Image;

   ----------
   -- Hash --
   ----------

   function Hash (Name : Reference_Name) return Interfaces.Unsigned_32 is
   begin
      return Interfaces.Unsigned_32
        (Hash (Name.Name) + Reference_Kind'Pos (Name.Kind));
   end Hash;

   ----------
   -- Hash --
   ----------

   function Hash (Name : Qualified_Name) return Header_Num is
   begin
      return (Hash (Name.NS) + Hash (Name.Local)) / 2;
   end Hash;

   ----------
   -- Hash --
   ----------

   function Hash (Name : Sax.Symbols.Symbol) return Header_Num is
   begin
      if Name = No_Symbol then
         return 0;
      else
         return Header_Num
           (Sax.Symbols.Hash (Name)
            mod Interfaces.Unsigned_32 (Header_Num'Last));
      end if;
   end Hash;

   --------------------------
   -- Validate_Simple_Type --
   --------------------------

   procedure Validate_Simple_Type
     (Reader      : access Abstract_Validation_Reader'Class;
      Simple_Type : Schema.Simple_Types.Simple_Type_Index;
      Ch          : Unicode.CES.Byte_Sequence;
      Loc         : Sax.Locators.Location;
      Insert_Id   : Boolean := True)
   is
      Error : Symbol;
      G : constant XML_Grammars.Encapsulated_Access := Get (Reader.Grammar);
   begin
      Validate_Simple_Type
        (Simple_Types  => G.NFA.Simple_Types,
         Enumerations  => G.NFA.Enumerations,
         Notations     => G.NFA.Notations,
         Symbols       => G.Symbols,
         Id_Table      => Reader.Id_Table,
         Insert_Id     => Insert_Id,
         Simple_Type   => Simple_Type,
         Ch            => Ch,
         Error         => Error,
         XML_Version   => Get_XML_Version (Reader.all));

      if Error /= No_Symbol then
         Validation_Error (Reader, Get (Error).all, Loc);
      end if;
   end Validate_Simple_Type;

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

   function Equal
     (Reader        : access Abstract_Validation_Reader'Class;
      Simple_Type   : Simple_Type_Index;
      Ch1           : Sax.Symbols.Symbol;
      Ch2           : Unicode.CES.Byte_Sequence) return Boolean
   is
      Is_Equal : Boolean;
      G : constant XML_Grammars.Encapsulated_Access := Get (Reader.Grammar);
   begin
      Equal
        (Simple_Types  => G.NFA.Simple_Types,
         Enumerations  => G.NFA.Enumerations,
         Notations     => G.NFA.Notations,
         Symbols       => G.Symbols,
         Id_Table      => Reader.Id_Table,
         Simple_Type   => Simple_Type,
         Ch1           => Ch1,
         Ch2           => Ch2,
         Is_Equal      => Is_Equal,
         XML_Version   => Get_XML_Version (Reader.all));
      return Is_Equal;
   end Equal;

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

   procedure Add_Facet
     (Grammar      : XML_Grammar;
      Facets       : in out Schema.Simple_Types.All_Facets;
      Facet_Name   : Sax.Symbols.Symbol;
      Value        : Sax.Symbols.Symbol;
      Loc          : Sax.Locators.Location) is
   begin
      Add_Facet
        (Facets,
         Symbols      => Get (Grammar).Symbols,
         Enumerations => Get (Grammar).NFA.Enumerations,
         Facet_Name   => Facet_Name,
         Value        => Value,
         Loc          => Loc);
   end Add_Facet;

   ---------------
   -- To_String --
   ---------------

   function To_String (Blocks : Block_Status) return String is
   begin
      return "{restr=" & Blocks (Block_Restriction)'Img
        & " ext=" & Blocks (Block_Extension)'Img
        & " sub=" & Blocks (Block_Substitution)'Img & '}';
   end To_String;

   ---------------------------------
   -- Check_Substitution_Group_OK --
   ---------------------------------

   procedure Check_Substitution_Group_OK
     (Handler            : access Abstract_Validation_Reader'Class;
      New_Type, Old_Type : Type_Index;
      Loc                : Sax.Locators.Location;
      Element_Block      : Block_Status)
   is
      NFA       : constant Schema_NFA_Access := Get_NFA (Handler.Grammar);
      Old_Descr : constant access Type_Descr := NFA.Get_Type_Descr (Old_Type);
      New_Descr : constant access Type_Descr := NFA.Get_Type_Descr (New_Type);
      Has_Restriction, Has_Extension : Boolean := False;

      Simple_Old_Type : Simple_Type_Index := No_Simple_Type_Index;
      --  Current target for [Old_Type], when considered a simple type

      function From_Descr_To_Old
        (Index : Type_Index; Descr : access Type_Descr) return Boolean;
      --  Try moving from [Descr] to [Old_Descr] through a series of extensions
      --  or restrictions. [False] is returned if we could not reach the old
      --  description.

      -----------------------
      -- From_Descr_To_Old --
      -----------------------

      function From_Descr_To_Old
        (Index : Type_Index; Descr : access Type_Descr) return Boolean
      is
         Result : Boolean := False;
         R      : access Type_Descr;
      begin
         if Index = Old_Type
           or else (Simple_Old_Type /= No_Simple_Type_Index
                    and then Descr.Simple_Content = Simple_Old_Type)
         then
            return True;
         end if;

         if Descr.Restriction_Of /= No_Type_Index then
            R := NFA.Get_Type_Descr (Descr.Restriction_Of);
            Has_Restriction := True;
            Result := From_Descr_To_Old (Descr.Restriction_Of, R);
         end if;

         if not Result and then Descr.Extension_Of /= No_Type_Index then
            R := NFA.Get_Type_Descr (Descr.Extension_Of);
            Has_Extension := True;
            Result := From_Descr_To_Old (Descr.Extension_Of, R);
         end if;

         return Result;
      end From_Descr_To_Old;

   begin
      if New_Type = Old_Type
        or else Old_Type = NFA.Get_Data (NFA.Ur_Type).Simple
        or else Old_Descr.Name = (NS    => Handler.XML_Schema_URI,
                                  Local => Handler.Anytype)
      then
         return;
      end if;

      if Element_Block (Block_Substitution) then
         Validation_Error (Handler, "Element blocks substitutions", Loc);
      end if;

      if Old_Descr.Simple_Content /= No_Simple_Type_Index then
         declare
            Simple : constant Simple_Type_Descr :=
              NFA.Get_Simple_Type (Old_Descr.Simple_Content);
         begin
            case Simple.Kind is
            when Primitive_Union =>
               for U in Simple.Union'Range loop
                  if Simple.Union (U) /= No_Simple_Type_Index then
                     Simple_Old_Type := Simple.Union (U);
                     if From_Descr_To_Old (New_Type, New_Descr) then
                        return;
                     end if;
                  end if;
               end loop;

               Validation_Error
                 (Handler,
                  To_QName (New_Descr.Name)
                  & " is not a derivation of union "
                  & To_QName (Old_Descr.Name),
                  Loc);

            when Primitive_List =>
               Validation_Error
                 (Handler,
                  To_QName (New_Descr.Name)
                  & " is not a derivation of list "
                  & To_QName (Old_Descr.Name),
                  Loc);

            when others =>
               null;   --  Will be dealt with below
            end case;
         end;
      end if;

      if not From_Descr_To_Old (New_Type, New_Descr) then
         Validation_Error
           (Handler,
            To_QName (New_Descr.Name)
            & " is not a derivation of " & To_QName (Old_Descr.Name), Loc);
      end if;

      if Has_Restriction and then Old_Descr.Block (Block_Restriction) then
         Validation_Error
           (Handler, To_QName (Old_Descr.Name) & " blocks restrictions", Loc);
      end if;

      if Has_Restriction and then Element_Block (Block_Restriction) then
         Validation_Error (Handler, "Element blocks restrictions", Loc);
      end if;

      if Has_Extension and then Old_Descr.Block (Block_Extension) then
         Validation_Error
           (Handler, To_QName (Old_Descr.Name) & " blocks extensions", Loc);
      end if;

      if Has_Extension and then Element_Block (Block_Extension) then
         Validation_Error (Handler, "Element blocks extensions", Loc);
      end if;
   end Check_Substitution_Group_OK;

   ------------------
   -- Dump_Dot_NFA --
   ------------------

   function Dump_Dot_NFA
     (Grammar            : XML_Grammar;
      Nested             : Nested_NFA := No_Nested) return String
   is
      NFA : constant Schema_NFA_Access := Get (Grammar).NFA;
   begin
      if Nested = No_Nested then
         return Schema_State_Machines_PP.Dump
           (NFA,
            Mode                => Dump_Dot_Compact,
            Show_Details        => True,
            Show_Isolated_Nodes => False,
            Since               => NFA.Metaschema_NFA_Last);
      else
         return Schema_State_Machines_PP.Dump
           (NFA,
            Nested              => Nested,
            Mode                => Dump_Dot_Compact);
      end if;
   end Dump_Dot_NFA;

   --------------
   -- Expected --
   --------------

   function Expected
     (Self       : Abstract_NFA_Matcher'Class;
      From_State, To_State : State;
      Parent_Data : access Active_State_Data;
      Trans      : Transition_Descr) return String
   is
      pragma Unreferenced (Self, From_State, To_State);
      Mask : Visited_All_Children;
   begin
      case Trans.Kind is
         when Transition_Symbol_From_All =>
            --  Only if the element has not been visited yet

            Mask := 2 ** Trans.All_Child_Index;
            if (Parent_Data.Visited and Mask) = 0 then
               return Image (Trans);
            end if;

         when Transition_Close_From_All =>
            --  Only if all children have been visited.
            if (Parent_Data.Visited and Trans.Mask) = Trans.Mask then
               return "close parent";
            end if;

         when others =>
            return Image (Trans);
      end case;

      return "";
   end Expected;

   -----------
   -- Match --
   -----------

   function Match
     (Self       : access Abstract_NFA_Matcher'Class;
      From_State, To_State : State;
      Parent_Data : access Active_State_Data;
      Trans      : Transition_Descr;
      Sym        : Transition_Event) return Boolean
   is
      pragma Unreferenced (To_State);
      Result : Boolean;
      Mask   : Visited_All_Children;
   begin
      case Trans.Kind is
         when Transition_Close =>
            Result := Sym.Closing;

         when Transition_Symbol | Transition_Symbol_From_All =>
            if Sym.Closing then
               Result := False;
            else
               if From_State = Start_State then
                  --  At toplevel, always qualified
                  Result := Trans.Name = Sym.Name;
               else
                  case Trans.Form is
                  when Unqualified =>
                     Result := (NS => Empty_String, Local => Trans.Name.Local)
                       = Sym.Name;
                  when Qualified =>
                     Result := Trans.Name = Sym.Name;
                  end case;
               end if;
            end if;

            if Result and then Trans.Kind = Transition_Symbol_From_All then
               --  Check that the transition hasn't been visited yet

               Mask := 2 ** Trans.All_Child_Index;

               if (Parent_Data.Visited and Mask) = 1 then
                  Result := False;
               else
                  Parent_Data.Visited := Parent_Data.Visited or Mask;
                  Result := True;
               end if;
            end if;

         when Transition_Close_From_All =>
            --  Check that all children have been visited or are optional
            Result := ((Parent_Data.Visited and Trans.Mask) = Trans.Mask)
              and then Sym.Closing;

         when Transition_Any =>
            if Sym.Closing then
               Result := False;
            else
               Result := Match_Any (Trans.Any, Sym.Name);
               if Result then
                  Schema_NFA_Matcher (Self.all).Matched_Through_Any := True;
                  Schema_NFA_Matcher (Self.all).Matched_Process_Content :=
                    Trans.Any.Process_Contents;
               end if;
            end if;
      end case;

      return Result;
   end Match;

   --------------
   -- Do_Match --
   --------------

   procedure Do_Match
     (Matcher         : in out Schema_NFA_Matcher;
      Sym             : Transition_Event;
      Success         : out Boolean;
      Through_Any     : out Boolean;
      Through_Process : out Process_Contents_Type)
   is
   begin
      Matcher.Matched_Through_Any := False;
      Matcher.Matched_Process_Content := Process_Strict;
      Process (Matcher, Input => Sym, Success => Success);
      Through_Any     := Matcher.Matched_Through_Any;
      Through_Process := Matcher.Matched_Process_Content;
   end Do_Match;

   ------------------
   -- Add_Notation --
   ------------------

   procedure Add_Notation
     (NFA : access Schema_NFA'Class; Name : Sax.Symbols.Symbol) is
   begin
      Symbol_Htable.Set (NFA.Notations, Name);
   end Add_Notation;

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

   procedure Free (Reader : in out Abstract_Validation_Reader) is
   begin
      Free (Reader.Id_Table);
   end Free;

end Schema.Validators;
