Back to... Excel Writer

Source file : excel_out.ads



-------------------------------------------------------------------------------------
--
--  EXCEL_OUT - A low level package for writing Microsoft Excel (*) files
--
--  Pure Ada 95 code, 100% portable: OS-, CPU- and compiler- independent.
--
--  Version / date / download info: see the version, reference, web strings
--   defined at the end of the public part of this package.

--  Legal licensing note:

--  Copyright (c) 2009 .. 2023 Gautier de Montmollin

--  Permission is hereby granted, free of charge, to any person obtaining a copy
--  of this software and associated documentation files (the "Software"), to deal
--  in the Software without restriction, including without limitation the rights
--  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
--  copies of the Software, and to permit persons to whom the Software is
--  furnished to do so, subject to the following conditions:

--  The above copyright notice and this permission notice shall be included in
--  all copies or substantial portions of the Software.

--  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
--  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
--  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
--  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
--  THE SOFTWARE.

--  NB: this is the MIT License, as found 12-Sep-2007 on the site
--  http://www.opensource.org/licenses/mit-license.php

--  (*) All Trademarks mentioned are properties of their respective owners.
-------------------------------------------------------------------------------------
--
--  Follow these steps to create an Excel spreadsheet stream:
--
--  1. Create
--
--  2. Optional settings, before any data output:
--     | Write_default_column_width
--     | Write_column_width for specific columns
--     | Write_default_row_height
--     | Write_row_height for specific rows
--     | Define_Font, then Define_Format
--
--  3. | Write(xl, row, column, data): row by row, column by column
--     | Put(xl, data)               : same, but column is auto-incremented
--     | New_Line(xl),...            : other "Text_IO"-like (full list below)
--     | Use_format, influences the format of data written next
--
--  4. Close
--
--  5. (Excel_Out_String only) function Contents returns the full .xls
--------------------------------------------------------------------------

with Ada.Calendar,
     Ada.Streams.Stream_IO,
     Ada.Strings.Unbounded,
     Ada.Text_IO;

package Excel_Out is

  -----------------------------------------------------------------
  -- The abstract Excel output stream root type.                 --
  -- From this package, you can use the following derived types: --
  --    * Excel_Out_File    : output in a file                   --
  --    * Excel_Out_String  : output in a string                 --
  -- Of course you can define your own derived types.            --
  -----------------------------------------------------------------

  type Excel_Out_Stream is abstract tagged private;

  ----------------------------------
  -- (2) Before any cell content: --
  ----------------------------------

  --  * Page layout for printing
  procedure Header (xl : Excel_Out_Stream; page_header_string : String);
  procedure Footer (xl : Excel_Out_Stream; page_footer_string : String);
  --
  procedure Left_Margin (xl : Excel_Out_Stream; inches : Long_Float);
  procedure Right_Margin (xl : Excel_Out_Stream; inches : Long_Float);
  procedure Top_Margin (xl : Excel_Out_Stream; inches : Long_Float);
  procedure Bottom_Margin (xl : Excel_Out_Stream; inches : Long_Float);
  procedure Margins
    (xl : Excel_Out_Stream;
     left_inches,
     right_inches,
     top_inches,
     bottom_inches : Long_Float);
  --
  procedure Print_Row_Column_Headers (xl : Excel_Out_Stream);
  procedure Print_Gridlines (xl : Excel_Out_Stream);
  --
  type Orientation_choice is (landscape, portrait);
  type Scale_or_fit_choice is (scale, fit);
  procedure Page_Setup (
    xl                     : Excel_Out_Stream;
    scaling_percents       : Positive := 100;
    fit_width_with_n_pages : Natural := 1; -- 0: as many as possible
    fit_height_with_n_pages : Natural := 1; -- 0: as many as possible
    orientation            : Orientation_choice := portrait;
    scale_or_fit           : Scale_or_fit_choice := scale
  );

  --  * The column width unit is as it appears in Excel when you resize a column.
  --      It is the width of a '0' in a standard font.
  procedure Write_default_column_width (xl : in out Excel_Out_Stream; width : Positive);
  procedure Write_column_width (xl : in out Excel_Out_Stream; column : Positive; width : Natural);
  procedure Write_column_width (
    xl            : in out Excel_Out_Stream;
    first_column,
    last_column   : Positive;
    width         : Natural
  );

  --  * The row height unit is in font points, as appearing when you
  --      resize a row in Excel. A zero height means the row is hidden.
  procedure Write_default_row_height (xl : Excel_Out_Stream; height : Positive);
  procedure Write_row_height (xl : Excel_Out_Stream; row : Positive; height : Natural);

  ----------------------
  -- Formatting cells --
  ----------------------
  --  A cell format is, as you can see in the format dialog
  --  in Excel, a combination of:
  --    - a number format
  --    - a set of alignements
  --    - a font
  --    - and other optional things to come here...
  --  Formats are user-defined except one which is predefined: Default_format

  type Format_type is private;

  function Default_format (xl : Excel_Out_Stream) return Format_type;
  --  What you get when creating a new sheet in Excel: Default_font,...

  --  * Number format
  type Number_format_type is private;

  --  Built-in number formats
  general          : constant Number_format_type;
  decimal_0        : constant Number_format_type;
  decimal_2        : constant Number_format_type;
  decimal_0_thousands_separator : constant Number_format_type;  -- 1'234'000
  decimal_2_thousands_separator : constant Number_format_type;  -- 1'234'000.00
  percent_0        : constant Number_format_type;   --  3%, 0%, -4%
  percent_2        : constant Number_format_type;
  percent_0_plus   : constant Number_format_type;   -- +3%, 0%, -4%
  percent_2_plus   : constant Number_format_type;
  scientific       : constant Number_format_type;
  dd_mm_yyyy       : constant Number_format_type;
  dd_mm_yyyy_hh_mm : constant Number_format_type;
  hh_mm            : constant Number_format_type;
  hh_mm_ss         : constant Number_format_type;

  procedure Define_Number_Format
    (xl            : in out Excel_Out_Stream;
     format        :    out Number_format_type;
     format_string : in     String);

  --  * Fonts are user-defined, one is predefined: Default_font
  type Font_type is private;

  function Default_font (xl : Excel_Out_Stream) return Font_type;
  --  Arial 10, regular, "automatic" color

  type Color_type is
    (automatic,
     black, white, red, green, blue, yellow, magenta, cyan,
     dark_red, dark_green, dark_blue, olive, purple, teal, silver, grey);

  type Font_style is private;

  --  For combining font styles (e.g.: bold & underlined):
  function "&" (a, b : Font_style) return Font_style;

  regular     : constant Font_style;
  italic      : constant Font_style;
  bold        : constant Font_style;
  bold_italic : constant Font_style;
  underlined  : constant Font_style;
  struck_out  : constant Font_style;
  shadowed    : constant Font_style;
  condensed   : constant Font_style;
  extended    : constant Font_style;

  procedure Define_Font
    (xl           : in out Excel_Out_Stream;
     font_name    :        String;
     height       :        Positive;
     font         :    out Font_type;
     --  Optional:
     style        :        Font_style := regular;
     color        :        Color_type := automatic);

  type Horizontal_alignment is (
    general_alignment, to_left, centred, to_right, filled,
    justified, centred_across_selection, -- (BIFF4-BIFF8)
    distributed -- (BIFF8, Excel 10.0 ("XP") and later only)
  );

  type Vertical_alignment is (
    top_alignment, centred, bottom_alignment,
    justified, -- (BIFF5-BIFF8)
    distributed -- (BIFF8, Excel 10.0 ("XP") and later only)
  );

  type Text_orientation is (
    normal,
    stacked,     -- vertical, top to bottom
    rotated_90,  -- vertical, rotated 90 degrees counterclockwise
    rotated_270  -- vertical, rotated 90 degrees clockwise
  );

  type Cell_border is private;

  --  Operator for combining borders (e.g.: left & top):
  function "&" (a, b : Cell_border) return Cell_border;

  no_border : constant Cell_border;
  left      : constant Cell_border;
  right     : constant Cell_border;
  top       : constant Cell_border;
  bottom    : constant Cell_border;
  box       : constant Cell_border;

  procedure Define_Format
    (xl               : in out Excel_Out_Stream;
     font             : in     Font_type;          -- Default_font(xl), or given by Define_font
     number_format    : in     Number_format_type; -- built-in, or given by Define_number_format
     cell_format      :    out Format_type;
     -- Optional parameters --
     horizontal_align : in     Horizontal_alignment := general_alignment;
     border           : in     Cell_border := no_border;
     shaded           : in     Boolean := False;    -- Add a dotted background pattern
     background_color : in     Color_type := automatic;
     wrap_text        : in     Boolean := False;
     vertical_align   : in     Vertical_alignment := bottom_alignment;
     text_orient      : in     Text_orientation := normal);

  ------------------------
  -- (3) Cell contents: --
  ------------------------

  --  NB: you need to write with ascending row index and with ascending
  --      column index within a row; otherwise Excel issues a protest

  procedure Write (xl : in out Excel_Out_Stream; r, c : Positive; num : Long_Float);
  procedure Write (xl : in out Excel_Out_Stream; r, c : Positive; num : Integer);
  procedure Write (xl : in out Excel_Out_Stream; r, c : Positive; str : String);
  procedure Write (xl : in out Excel_Out_Stream; r, c : Positive; str : Ada.Strings.Unbounded.Unbounded_String);
  procedure Write (xl : in out Excel_Out_Stream; r, c : Positive; date : Ada.Calendar.Time);

  --  "Ada.Text_IO" - like output.
  --  No need to specify row & column each time.
  --  Write 'Put(x, content)' where x is an Excel_Out_Stream just
  --  as if x was a File_Type, and vice-versa.
  --
  procedure Put (xl : in out Excel_Out_Stream; num : Long_Float);
  procedure Put (xl    : in out Excel_Out_Stream;
                num   : in Integer;
                width : in Ada.Text_IO.Field := 0; -- ignored
                base  : in Ada.Text_IO.Number_Base := 10
            );
  procedure Put (xl : in out Excel_Out_Stream; str : String);
  procedure Put (xl : in out Excel_Out_Stream; str : Ada.Strings.Unbounded.Unbounded_String);
  procedure Put (xl : in out Excel_Out_Stream; date : Ada.Calendar.Time);
  --
  procedure Put_Line (xl : in out Excel_Out_Stream; num : Long_Float);
  procedure Put_Line (xl : in out Excel_Out_Stream; num : Integer);
  procedure Put_Line (xl : in out Excel_Out_Stream; str : String);
  procedure Put_Line (xl : in out Excel_Out_Stream; str : Ada.Strings.Unbounded.Unbounded_String);
  procedure Put_Line (xl : in out Excel_Out_Stream; date : Ada.Calendar.Time);
  --
  procedure New_Line (xl : in out Excel_Out_Stream; Spacing : Positive := 1);

  --  Get current column and row. The next Put will put contents in that cell.
  --
  function Col (xl : in Excel_Out_Stream) return Positive;    -- Text_IO naming
  function Column (xl : in Excel_Out_Stream) return Positive; -- Excel naming
  function Line (xl : in Excel_Out_Stream) return Positive;   -- Text_IO naming
  function Row (xl : in Excel_Out_Stream) return Positive;    -- Excel naming

  --  Relative / absolute jumps
  procedure Jump (xl : in out Excel_Out_Stream; rows, columns : Natural);
  procedure Jump_to (xl : in out Excel_Out_Stream; to_row, to_column : Positive);
  procedure Next (xl : in out Excel_Out_Stream; columns : Natural := 1);  -- Jump 0 or more cells right
  procedure Next_Row (xl : in out Excel_Out_Stream; rows : Natural := 1); -- Jump 0 or more cells down
  --
  --  Merge a certain amount of cells with the last one,
  --  right to that cell, on the same row.
  procedure Merge (xl : in out Excel_Out_Stream; cells : Positive);

  procedure Write_cell_comment (xl : Excel_Out_Stream; at_row, at_column : Positive; text : String);
  procedure Write_cell_comment_at_cursor (xl : Excel_Out_Stream; text : String);

  --  Cells written after Use_format will be using the given format,
  --  defined by Define_format.
  procedure Use_format (
    xl           : in out Excel_Out_Stream;
    format       : in     Format_type
  );
  procedure Use_default_format (xl : in out Excel_Out_Stream);

  --  The Freeze Pane methods can be called anytime before Close
  procedure Freeze_Panes (xl : in out Excel_Out_Stream; at_row, at_column : Positive);
  procedure Freeze_Panes_at_cursor (xl : in out Excel_Out_Stream);
  procedure Freeze_Top_Row (xl : in out Excel_Out_Stream);
  procedure Freeze_First_Column (xl : in out Excel_Out_Stream);

  --  Zoom level. Example: for 85%, call with parameters 85, 100.
  procedure Zoom_level (xl : in out Excel_Out_Stream; numerator, denominator : Positive);

  --  Set_Index and Index are not directly useful for Excel_Out users.
  --  They are private indeed, but they must be visible (RM 3.9.3(10)).

  --  Set the index on the stream
  procedure Set_Index (xl : in out Excel_Out_Stream;
                       to : Ada.Streams.Stream_IO.Positive_Count)
  is abstract;

  --  Return the index of the stream
  function Index (xl : Excel_Out_Stream) return Ada.Streams.Stream_IO.Count
  is abstract;

  Excel_stream_not_created,
  Excel_stream_not_closed,
  Decreasing_row_index,
  Decreasing_column_index,
  Row_out_of_range,
  Column_out_of_range,
  Format_out_of_range,
  Font_out_of_range,
  Number_format_out_of_range : exception;

  type Excel_type is (
    BIFF2,    --  Excel 2.1, 2,2
    BIFF3,    --  Excel 3.0
    BIFF4     --  Excel 4.0
    --  BIFF5,    --  Excel 5.0 to 7.0
    --  BIFF8     --  Excel 8.0 (97) to 11.0 (2003) - UTF-16 support
  );

  Default_Excel_type : constant Excel_type := BIFF4;

  --  Assumed encoding for types Character and String (8-bit):

  type Encoding_type is (
    Windows_CP_874,  --  Thai
    Windows_CP_932,  --  Japanese Shift-JIS
    Windows_CP_936,  --  Chinese Simplified GBK
    Windows_CP_949,  --  Korean (Wansung)
    Windows_CP_950,  --  Chinese Traditional BIG5
    Windows_CP_1250, --  Latin II (Central European)
    Windows_CP_1251, --  Cyrillic
    Windows_CP_1252, --  Latin I, superset of ISO 8859-1
    Windows_CP_1253, --  Greek
    Windows_CP_1254, --  Turkish
    Windows_CP_1255, --  Hebrew
    Windows_CP_1256, --  Arabic
    Windows_CP_1257, --  Baltic
    Windows_CP_1258, --  Vietnamese
    Windows_CP_1361, --  Korean (Johab)
    Apple_Roman
  );

  Default_encoding : constant Encoding_type := Windows_CP_1252;

  -------------------------------------------------------------------
  --  Here, the derived stream types pre-defined in this package.  --
  -------------------------------------------------------------------
  --  * Output to a file:

  type Excel_Out_File is new Excel_Out_Stream with private;

  procedure Create (
    xl           : in out Excel_Out_File;
    file_name    :        String;
    excel_format :        Excel_type    := Default_Excel_type;
    encoding     :        Encoding_type := Default_encoding
  );

  procedure Close (xl : in out Excel_Out_File);

  function Is_Open (xl : in Excel_Out_File) return Boolean;

  --  * Output to a string (to be compressed, packaged, transmitted, ... ):

  type Excel_Out_String is new Excel_Out_Stream with private;

  procedure Create (
    xl           : in out Excel_Out_String;
    excel_format :        Excel_type    := Default_Excel_type;
    encoding     :        Encoding_type := Default_encoding
  );

  procedure Close (xl : in out Excel_Out_String);

  function Contents (xl : Excel_Out_String) return String;

  ----------------------------------------------------------------
  --  Information about this package - e.g. for an "about" box  --
  ----------------------------------------------------------------

  version   : constant String := "18";
  reference : constant String := "25-Jun-2023";
  --  Hopefully the latest version is at one of those URLs:
  web       : constant String := "http://excel-writer.sf.net/";
  web2 : constant String := "https://sourceforge.net/projects/excel-writer/";
  web3 : constant String := "https://github.com/zertovitch/excel-writer";
  web4 : constant String := "https://alire.ada.dev/crates/excel_writer";

private

  ----------------------------------------
  -- Raw Streams, with 'Read and 'Write --
  ----------------------------------------

  type XL_Raw_Stream_Class is access all Ada.Streams.Root_Stream_Type'Class;

  type Font_type is new Natural;
  type Number_format_type is new Natural;
  type Format_type is new Natural;

  subtype XF_Range is Integer range 0 .. 62;
  --  ^ After 62 we would need to use an IXFE (5.62)

  --  Theoretically, we would not need to memorize the XF informations
  --  and just give the XF identifier given with Format_type, but some
  --  versions of Excel with some locales mix up the font and numerical format
  --  when giving 0 for the cell attributes (see Cell_attributes, 2.5.13)
  --   Added Mar-2011.

  type XF_Info is record
    font : Font_type;
    numb : Number_format_type;
  end record;

  type XF_Definition is array (XF_Range) of XF_Info;

  --  Built-in number formats
  general          : constant Number_format_type := 0;
  decimal_0        : constant Number_format_type := 1;
  decimal_2        : constant Number_format_type := 2;
  decimal_0_thousands_separator : constant Number_format_type := 3;  -- 1'234'000
  decimal_2_thousands_separator : constant Number_format_type := 4;  -- 1'234'000.00
  no_currency_0       : constant Number_format_type := 5;
  no_currency_red_0   : constant Number_format_type := 6;
  no_currency_2       : constant Number_format_type := 7;
  no_currency_red_2   : constant Number_format_type := 8;
  currency_0       : constant Number_format_type :=  9; -- 5 in BIFF2, BIFF3 (sob!)
  currency_red_0   : constant Number_format_type := 10; -- 6 in BIFF2, BIFF3...
  currency_2       : constant Number_format_type := 11;
  currency_red_2   : constant Number_format_type := 12;
  percent_0        : constant Number_format_type := 13;  --  3%, 0%, -4%
  percent_2        : constant Number_format_type := 14;
  scientific       : constant Number_format_type := 15;
  fraction_1       : constant Number_format_type := 16;
  fraction_2       : constant Number_format_type := 17;
  dd_mm_yyyy       : constant Number_format_type := 18; -- 14 in BIFF3, 12 in BIFF2 (re-sob!)
  dd_mmm_yy        : constant Number_format_type := 19; -- 15 in BIFF3, 13 in BIFF2...
  dd_mmm           : constant Number_format_type := 20;
  mmm_yy           : constant Number_format_type := 21;
  h_mm_AM_PM       : constant Number_format_type := 22;
  h_mm_ss_AM_PM    : constant Number_format_type := 23;
  hh_mm            : constant Number_format_type := 24;
  hh_mm_ss         : constant Number_format_type := 25;
  dd_mm_yyyy_hh_mm : constant Number_format_type := 26;
  --  End of Excel built-in formats
  last_built_in : constant Number_format_type := dd_mm_yyyy_hh_mm;

  percent_0_plus   : constant Number_format_type := 27; -- +3%, 0%, -4%
  percent_2_plus   : constant Number_format_type := 28;
  date_iso         : constant Number_format_type := 29; -- ISO 8601 format: 2014-03-16
  date_h_m_iso     : constant Number_format_type := 30; -- date, hour, minutes
  date_h_m_s_iso   : constant Number_format_type := 31; -- date, hour, minutes, seconds
  --  End of our custom formats
  last_custom_number_format : constant Number_format_type := date_h_m_s_iso;

  type Col_width_set is array (1 .. 256) of Boolean;

  --  We have a concrete type as hidden ancestor of the Excel_Out_Stream root
  --  type. A variable of that type is initialized with default values and
  --  can help re-initialize a Excel_Out_Stream when re-used several times.
  --  See the Reset procedure in body.
  --  The abstract Excel_Out_Stream could have default values, but using a
  --  variable of this type to reset values is not Ada compliant (LRM:3.9.3(8))
  --
  type Excel_Out_Pre_Root_Type is tagged record
    xl_stream  : XL_Raw_Stream_Class;
    xl_format  : Excel_type    := Default_Excel_type;
    encoding   : Encoding_type := Default_encoding;
    dimrecpos  : Ada.Streams.Stream_IO.Positive_Count;
    maxcolumn  : Positive := 1;
    maxrow     : Positive := 1;
    fonts      : Integer := -1; -- [-1..max_font]
    xfs        : Integer := -1; -- [-1..XF_Range'Last]
    xf_in_use  : XF_Range := 0;
    xf_def     : XF_Definition;
    number_fmt : Number_format_type := last_custom_number_format;
    def_font   : Font_type;
    def_fmt    : Format_type; -- Default format; used for "Normal" style
    cma_fmt    : Format_type; -- Format used for defining "Comma" style
    ccy_fmt    : Format_type; -- Format used for defining "Currency" style
    pct_fmt    : Format_type; -- Format used for defining "Percent" style
    is_created : Boolean := False;
    is_closed  : Boolean := False;
    curr_row   : Positive := 1;
    curr_col   : Positive := 1;
    frz_panes  : Boolean := False;
    freeze_row : Positive;
    freeze_col : Positive;
    zoom_num   : Positive := 100;
    zoom_den   : Positive := 100;
    defcolwdth : Natural := 0; -- 0 = not set; 1/256 of the width of the zero character
    std_col_width : Col_width_set := (others => True);
  end record;

  type Excel_Out_Stream is abstract new Excel_Out_Pre_Root_Type with null record;

  type Font_style_single is
    (bold_single,
     italic_single,
     underlined_single,
     struck_out_single,
     outlined_single,
     shadowed_single,
     condensed_single,
     extended_single);

  type Font_style is array (Font_style_single) of Boolean;

  regular     : constant Font_style := (others => False);
  italic      : constant Font_style := (italic_single => True, others => False);
  bold        : constant Font_style := (bold_single => True, others => False);
  bold_italic : constant Font_style := bold or italic;
  underlined  : constant Font_style := (underlined_single => True, others => False);
  struck_out  : constant Font_style := (struck_out_single => True, others => False);
  shadowed    : constant Font_style := (shadowed_single => True, others => False);
  condensed   : constant Font_style := (condensed_single => True, others => False);
  extended    : constant Font_style := (extended_single => True, others => False);

  type Cell_border_single is
    (left_single,
     right_single,
     top_single,
     bottom_single);

  type Cell_border is array (Cell_border_single) of Boolean;

  no_border : constant Cell_border := (others => False);
  left      : constant Cell_border := (left_single => True, others => False);
  right     : constant Cell_border := (right_single => True, others => False);
  top       : constant Cell_border := (top_single => True, others => False);
  bottom    : constant Cell_border := (bottom_single => True, others => False);
  box       : constant Cell_border := (others => True);

  ----------------------
  -- Output to a file --
  ----------------------

  type XL_file_acc is
    access Ada.Streams.Stream_IO.File_Type;

  type Excel_Out_File is new Excel_Out_Stream with record
    xl_file   : XL_file_acc := null; -- access to the "physical" Excel file
  end record;

  --  Set the index on the file
  procedure Set_Index (xl : in out Excel_Out_File;
                       To : Ada.Streams.Stream_IO.Positive_Count);

  --  Return the index of the file
  function Index (xl : Excel_Out_File) return Ada.Streams.Stream_IO.Count;

  ------------------------
  -- Output to a string --
  ------------------------
  --  Code reused from Zip_Streams

  --- *** We define here a complete in-memory stream:
  type Unbounded_Stream is new Ada.Streams.Root_Stream_Type with
    record
      Unb : Ada.Strings.Unbounded.Unbounded_String;
      Loc : Integer := 1;
    end record;

  --  Read data from the stream.
  procedure Read
    (Stream : in out Unbounded_Stream;
     Item   : out Ada.Streams.Stream_Element_Array;
     Last   : out Ada.Streams.Stream_Element_Offset);

  --  Write data to the stream, starting from the current index.
  --  Data will be overwritten from index is already available.
  procedure Write
    (Stream : in out Unbounded_Stream;
     Item   : Ada.Streams.Stream_Element_Array);

  --  Set the index on the stream
  procedure Set_Index (S : access Unbounded_Stream; To : Positive);

  --  returns the index of the stream
  function Index (S : access Unbounded_Stream) return Integer;

  --- ***

  type Unbounded_Stream_Acc is access Unbounded_Stream;

  type Excel_Out_String is new Excel_Out_Stream with record
    xl_memory : Unbounded_Stream_Acc;
  end record;

  --  Set the index on the Excel string stream
  procedure Set_Index (xl : in out Excel_Out_String;
                       To : Ada.Streams.Stream_IO.Positive_Count);

  --  Return the index of the Excel string stream
  function Index (xl : Excel_Out_String) return Ada.Streams.Stream_IO.Count;

end Excel_Out;


Excel Writer: Ada package writing Excel files (.xls). Ada programming.
Some news about Excel Writer and other Ada projects on Gautier's blog.