------------------------------------------------------------------------------- -- NAME (body) : localize-parser.adb -- AUTHOR : Pascal Pignard -- ROLE : Localization files parser unit. -- NOTES : Ada 2012, GNOGA 2.1 alpha -- -- COPYRIGHT : (c) Pascal Pignard 2021 -- LICENCE : CeCILL V2 (http://www.cecill.info) -- CONTACT : http://blady.pagesperso-orange.fr ------------------------------------------------------------------------------- with Ada.Characters.Wide_Wide_Latin_1; with Gnoga; with UXStrings.Formatting; with UXStrings.Text_IO; package body Localize.Parser is use type Content_Maps.Cursor; procedure Parse_Strings_File (File_Name : String; Content : out Property_List) is type State_Type is (None, In_Comment, In_Key, In_Value, Equal, Semi_Colon); Raw_File : UXStrings.Text_IO.File_Type; Text : String; C : Unicode_Character; I : Natural; State : State_Type := None; Comment : String; Key : String; Value : String; procedure Append (C : Unicode_Character) is begin case State is when In_Comment => Append (Comment, C); when In_Key => Append (Key, C); when In_Value => Append (Value, C); when others => null; end case; end Append; begin UXStrings.Text_IO.Open (Raw_File, UXStrings.Text_IO.In_File, File_Name, UTF_16LE, UXStrings.Text_IO.LF_Ending); while not UXStrings.Text_IO.End_Of_File (Raw_File) loop UXStrings.Text_IO.Get (Raw_File, C); Text.Append (C); end loop; UXStrings.Text_IO.Close (Raw_File); Content.Clear; I := Text.First; while I <= Text.Last loop if Text (I) = '\' then Text.Next (I); case Text (I) is when 't' => Append (Ada.Characters.Wide_Wide_Latin_1.HT); when 'n' => Append (Ada.Characters.Wide_Wide_Latin_1.LF); when Ada.Characters.Wide_Wide_Latin_1.LF => null; when '"' | '\' => Append (Text (I)); when others => Append ('\'); Append (Text (I)); end case; elsif Text (I) = '/' and State = None then Text.Next (I); if Text (I) = '*' then State := In_Comment; end if; elsif Text (I) /= '*' and State = In_Comment then Append (Comment, Text (I)); elsif Text (I) = '*' and State = In_Comment then Text.Next (I); if Text (I) = '/' then State := None; else Append (Comment, '*'); Append (Comment, Text (I)); end if; elsif Text (I) = '"' and State = None then State := In_Key; elsif Text (I) /= '"' and State = In_Key then Append (Key, Text (I)); elsif Text (I) = '"' and State = In_Key then State := Equal; elsif Text (I) = '"' and State = Equal then State := In_Value; elsif Text (I) /= '"' and State = In_Value then Append (Value, Text (I)); elsif Text (I) = '"' and State = In_Value then State := Semi_Colon; elsif Text (I) = ';' and State = Semi_Colon then Content.Insert (Key, (Comment, Value, False)); Comment := Null_UXString; Key := Null_UXString; Value := Null_UXString; State := None; end if; Text.Next (I); end loop; end Parse_Strings_File; procedure Write_Strings_File (File_Name : String; Content : Property_List) is Raw_File : UXStrings.Text_IO.File_Type; procedure Escaped_Put (Str : String; Multi_Comment : Boolean := False) is begin for I in 1 .. Length (Str) loop if Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.HT then UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, 't'); elsif Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.LF then if Multi_Comment then UXStrings.Text_IO.New_Line (Raw_File); else UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, 'n'); end if; elsif Element (Str, I) in '"' | '\' then UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, Element (Str, I)); else UXStrings.Text_IO.Put (Raw_File, Element (Str, I)); end if; end loop; end Escaped_Put; begin UXStrings.Text_IO.Create (Raw_File, UXStrings.Text_IO.Out_File, File_Name, UTF_16LE, UXStrings.Text_IO.LF_Ending); UXStrings.Text_IO.Put_BOM (Raw_File); for C in Content.Iterate loop if Content_Maps.Element (C).Comment /= Null_UXString then UXStrings.Text_IO.Put (Raw_File, '/'); UXStrings.Text_IO.Put (Raw_File, '*'); Escaped_Put (Content_Maps.Element (C).Comment, True); UXStrings.Text_IO.Put (Raw_File, '*'); UXStrings.Text_IO.Put (Raw_File, '/'); UXStrings.Text_IO.New_Line (Raw_File); end if; UXStrings.Text_IO.Put (Raw_File, '"'); Escaped_Put (Content_Maps.Key (C)); UXStrings.Text_IO.Put (Raw_File, '"'); UXStrings.Text_IO.Put (Raw_File, ' '); UXStrings.Text_IO.Put (Raw_File, '='); UXStrings.Text_IO.Put (Raw_File, ' '); UXStrings.Text_IO.Put (Raw_File, '"'); Escaped_Put (Content_Maps.Element (C).Text); UXStrings.Text_IO.Put (Raw_File, '"'); UXStrings.Text_IO.Put (Raw_File, ';'); UXStrings.Text_IO.New_Line (Raw_File); end loop; UXStrings.Text_IO.Close (Raw_File); end Write_Strings_File; procedure Parse_Properties_File (File_Name : String; Content : out Property_List) is type State_Type is (None, In_Comment, In_Key, In_Value, Equal); Raw_File : UXStrings.Text_IO.File_Type; Text : String; C : Unicode_Character; I : Natural; State : State_Type := None; Comment : String; Key : String; Value : String; Hex4 : String := "0000"; procedure Append (C : Wide_Wide_Character) is begin case State is when In_Comment => Append (Comment, C); when In_Key => Append (Key, C); when In_Value => Append (Value, C); when None => State := In_Key; Append (Key, C); when Equal => State := In_Value; Append (Value, C); end case; end Append; begin UXStrings.Text_IO.Open (Raw_File, UXStrings.Text_IO.In_File, File_Name, Latin_1, UXStrings.Text_IO.LF_Ending); while not UXStrings.Text_IO.End_Of_File (Raw_File) loop UXStrings.Text_IO.Get (Raw_File, C); Text.Append (C); end loop; UXStrings.Text_IO.Close (Raw_File); Content.Clear; I := Text.First; while I <= Text.Last loop if Text (I) = '\' then Text.Next (I); case Text (I) is when 'u' => for J in Hex4 loop Text.Next (I); exit when I > Text.Last; Hex4.Replace_Unicode (J, Text (I)); end loop; Append (Wide_Wide_Character'Val (Gnoga.Value (Hex4, 16))); when ' ' | ':' | '=' | '#' | '!' | '\' => Append (Text (I)); when 't' => Append (Ada.Characters.Wide_Wide_Latin_1.HT); when 'n' => Append (Ada.Characters.Wide_Wide_Latin_1.LF); when Ada.Characters.Wide_Wide_Latin_1.LF => null; when others => Append ('\'); Append (Text (I)); end case; elsif Text (I) = Ada.Characters.Wide_Wide_Latin_1.LF and State = In_Comment then State := None; elsif State = In_Comment then Append (Comment, Text (I)); elsif Text (I) = Ada.Characters.Wide_Wide_Latin_1.LF and State in In_Value | Equal | In_Key then Content.Include (Key, (Comment, Value, False)); Comment := Null_UXString; Key := Null_UXString; Value := Null_UXString; State := None; elsif Text (I) in '#' | '!' and State = None then State := In_Comment; if Comment /= Null_UXString then Append (Ada.Characters.Wide_Wide_Latin_1.LF); end if; elsif Text (I) in '=' | ':' and State = In_Key then State := Equal; elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.LF | Ada.Characters.Wide_Wide_Latin_1.FF | ' ' and State in In_Key then Append (Key, (Text (I))); elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.LF | Ada.Characters.Wide_Wide_Latin_1.FF | ' ' and State in None then State := In_Key; Append (Key, Text (I)); elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.FF and State in In_Value then Append (Value, Text (I)); elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.FF | ' ' and State in Equal then State := In_Value; Append (Value, Text (I)); end if; Text.Next (I); end loop; end Parse_Properties_File; procedure Write_Properties_File (File_Name : String; Content : Property_List) is Raw_File : UXStrings.Text_IO.File_Type; type Escape_Space_Type is (No, Start, Full); procedure Escaped_Write (Str : String; Escape_Space : Escape_Space_Type; Multi_Comment : Boolean := False) is Space : Boolean := Escape_Space = Start; use UXStrings.Formatting; use all type UXStrings.Formatting.Alignment; function Format is new Integer_Format (Natural); function Format_U16 (Item : Natural; Base : in Number_Base := 16; PutPlus : in Boolean := False; Field : in Natural := 4; Justify : in Alignment := Right; Fill : in Character := '0') return UXString renames Format; begin for I in 1 .. Length (Str) loop if Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.HT then UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, 't'); Space := False; elsif Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.LF then if Multi_Comment then UXStrings.Text_IO.New_Line (Raw_File); UXStrings.Text_IO.Put (Raw_File, '#'); else UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, 'n'); end if; Space := False; elsif Element (Str, I) in '=' | ':' | '#' | '!' | '\' and Escape_Space /= No then UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, Element (Str, I)); Space := False; elsif Element (Str, I) = ' ' and (Space or Escape_Space = Full) then UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, ' '); Space := False; elsif Element (Str, I) < Ada.Characters.Wide_Wide_Latin_1.DEL then UXStrings.Text_IO.Put (Raw_File, Element (Str, I)); Space := False; else UXStrings.Text_IO.Put (Raw_File, '\'); UXStrings.Text_IO.Put (Raw_File, 'u'); declare Hex4 : constant String := Format_U16 (BMP_Character'Pos (Get_BMP (Str, I, '?'))); begin for C of Hex4 loop UXStrings.Text_IO.Put (Raw_File, C); end loop; end; Space := False; end if; end loop; end Escaped_Write; begin UXStrings.Text_IO.Create (Raw_File, UXStrings.Text_IO.Out_File, File_Name, Latin_1, UXStrings.Text_IO.LF_Ending); for C in Content.Iterate loop if Content_Maps.Element (C).Comment /= Null_UXString then UXStrings.Text_IO.Put (Raw_File, '#'); Escaped_Write (Content_Maps.Element (C).Comment, No, True); UXStrings.Text_IO.New_Line (Raw_File); end if; Escaped_Write (Content_Maps.Key (C), Full); UXStrings.Text_IO.Put (Raw_File, '='); Escaped_Write (Content_Maps.Element (C).Text, Start); UXStrings.Text_IO.New_Line (Raw_File); end loop; UXStrings.Text_IO.Close (Raw_File); end Write_Properties_File; procedure Read (Properties : out Property_List; File_Name : String) is begin if Tail (File_Name, 8, ' ') = ".strings" then Parse_Strings_File (File_Name, Properties); end if; if Tail (File_Name, 11, ' ') = ".properties" then Parse_Properties_File (File_Name, Properties); end if; end Read; procedure Write (Properties : Property_List; File_Name : String) is begin if Tail (File_Name, 8, ' ') = ".strings" then Write_Strings_File (File_Name, Properties); end if; if Tail (File_Name, 11, ' ') = ".properties" then Write_Properties_File (File_Name, Properties); end if; end Write; function Keys (Properties : Property_List) return Key_List is begin return Result_Key_List : Key_List do for Index in Properties.Iterate loop Result_Key_List.Append (Content_Maps.Key (Index)); end loop; end return; end Keys; function Contains (Properties : Property_List; Key : String) return Boolean is (Content_Maps.Contains (Content_Maps.Map (Properties), Key)); procedure Insert (Properties : in out Property_List; Key : String) is begin Properties.Insert (Key, (Null_UXString, Null_UXString, False)); end Insert; procedure Delete (Properties : in out Property_List; Key : String) is begin if Properties.Find (Key) /= Content_Maps.No_Element then Properties.Delete (Key); end if; end Delete; procedure Rename (Properties : in out Property_List; From, To : String) is begin Properties.Insert (To, Properties.Element (From)); Properties.Delete (From); end Rename; function Text (Properties : Property_List; Key : String) return String is begin if Properties.Find (Key) = Content_Maps.No_Element then return ""; else return Properties.Element (Key).Text; end if; end Text; procedure Text (Properties : in out Property_List; Key : String; Value : String) is Cursor : constant Content_Maps.Cursor := Properties.Find (Key); Element : Property_Type; begin if Cursor /= Content_Maps.No_Element then Element := Content_Maps.Element (Cursor); Element.Text := Value; if Element.Text /= Content_Maps.Element (Cursor).Text then Element.Modified := True; Properties.Replace_Element (Cursor, Element); end if; end if; end Text; function Comment (Properties : Property_List; Key : String) return String is begin if Properties.Find (Key) = Content_Maps.No_Element then return Null_UXString; else return Properties.Element (Key).Comment; end if; end Comment; procedure Comment (Properties : in out Property_List; Key : String; Value : String) is Cursor : constant Content_Maps.Cursor := Properties.Find (Key); Element : Property_Type; begin if Cursor /= Content_Maps.No_Element then Element := Content_Maps.Element (Cursor); Element.Comment := Value; if Element.Comment /= Content_Maps.Element (Cursor).Comment then Element.Modified := True; Properties.Replace_Element (Cursor, Element); end if; end if; end Comment; function Modified (Properties : Property_List; Key : String) return Boolean is begin if Properties.Find (Key) = Content_Maps.No_Element then return False; else return Properties.Element (Key).Modified; end if; end Modified; procedure Reset_Modified_Indicators (Properties : in out Property_List) is begin for Element of Properties loop Element.Modified := False; end loop; end Reset_Modified_Indicators; function Selected_Keys (Master, Locale : Property_List; Pattern : String) return Key_List is package Keys_Sorting is new Lists.Generic_Sorting; begin if Pattern /= "" then return Result_Key_List : Key_List do for Cursor in Master.Iterate loop if (Index (Content_Maps.Key (Cursor), Pattern) > 0 or Index (Content_Maps.Element (Cursor).Text, Pattern) > 0 or Index (Content_Maps.Element (Cursor).Comment, Pattern) > 0) and not Result_Key_List.Contains (Content_Maps.Key (Cursor)) then Result_Key_List.Append (Content_Maps.Key (Cursor)); end if; end loop; for Cursor in Locale.Iterate loop if (Index (Content_Maps.Key (Cursor), Pattern) > 0 or Index (Content_Maps.Element (Cursor).Text, Pattern) > 0 or Index (Content_Maps.Element (Cursor).Comment, Pattern) > 0) and not Result_Key_List.Contains (Content_Maps.Key (Cursor)) then Result_Key_List.Append (Content_Maps.Key (Cursor)); end if; end loop; end return; else return Result_Key_List : Key_List := Keys (Master) do for Cursor in Locale.Iterate loop if not Result_Key_List.Contains (Content_Maps.Key (Cursor)) then Result_Key_List.Append (Content_Maps.Key (Cursor)); end if; end loop; Keys_Sorting.Sort (Result_Key_List); end return; end if; end Selected_Keys; end Localize.Parser;