#############################################################################
##
##    blockdesign_io.g          Design 1.8 Package          Leonard Soicher
##
##    
# The "GAP Expander/Writer" to expand the information about 
# given (lists of) binary block designs, and to print 
# this information in DTRS external representation XML-format.
#
# The "GAP Reader" to read in (lists of) binary block designs in 
# DTRS external representation XML-format, and to convert these 
# block designs into GAP-format.
#
# Copyright (C) 2003-2023 Leonard H. Soicher
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

DESIGN_DTRS_PROTOCOL := "2.0";

BindGlobal("extrep_index",function(i)
#
# Returns the external representation index corresponding to the GAP index i.
#
return i-1;
end);

BindGlobal("extrep_lessthan_onelevel",function(x,y)
#
# This function assumes that the elements of the lists x and y are GAP-sorted
# and that for these elements, GAP less than is the same as extrep less than.
# Under these assumptions, this boolean function returns true iff 
# x is less than y in the (length-first, lex) extrep ordering.
#
return Length(x) < Length(y) or (Length(x) = Length(y) and x < y);
end);

BindGlobal("extrep_ordered_blocks",function(D)
#
# Returns the blocks of the block design D, ordered correctly
# for the external representation.
# 
local extrep_blocks;
extrep_blocks := ShallowCopy(D.blocks);
Sort(extrep_blocks,extrep_lessthan_onelevel); 
return extrep_blocks;
end);

BindGlobal("infoXML",function(software,reference,note)
local info,x;
info:="<info>\n";
for x in software do
   Append(info,"<software>\n");
   Append(info,x);
   Append(info,"\n</software>\n");
od;   
for x in reference do
   Append(info,"<reference>\n");
   Append(info,x);
   Append(info,"\n</reference>\n");
od;   
for x in note do
   Append(info,"<note>\n");
   Append(info,x);
   Append(info,"\n</note>\n");
od;   
Append(info,"</info>\n");
return info;
end);

BindGlobal("zXML",function(i)
#
# Returns a string giving the external representation of the 
# integer i.
#
if not IsInt(i) then
   Error("<i> must be an integer");
fi;   
return Concatenation("<z>",String(i),"</z>\n");
end);

BindGlobal("index_flagXML",function(index,flag)
return Concatenation("<index_flag\n",
   " index=\"",String(index),"\"\n flag=\"",String(flag),"\"/>\n");
end);

BindGlobal("permutationXML",function(g,n)
#
# Returns the permutation  g  of degree  n  in external representation format.
#
local perm,x;
perm:="<permutation>\n";
for x in [1..n] do
   Append(perm,zXML(extrep_index(x^g)));
od;
Append(perm,"</permutation>\n");
return perm;
end);

BindGlobal("functionXML",function(domain_info,n,k,title,preimages,images)
#
# (pseudo) functions with codomain = integers.
#
# For an actual function, it is assumed that each preimage is already 
# sorted in lexicographic order, but no ordering of these preimages is assumed
# (a shallow copy of these preimages is sorted for external representation 
# output). 
#
local f,pre,im,i,x,y,on_indices,ordered;
on_indices:=not IsInt(k) or k<0;
pre:=ShallowCopy(preimages);
im:=ShallowCopy(images);
if ForAll(pre,x->IsList(x) and x<>"blank") then 
   ordered:=true;
   # possibly pre=["entire_domain"]
   if Length(pre)>1 then
      # Sort preimages and images in parallel according to the 
      # explicit preimages. It is assumed that each preimage is already 
      # sorted in lexicographic order.
      SortParallel(pre,im,extrep_lessthan_onelevel);
   fi;   
else
   # Do not assume anything about the order of the (virtual) preimages.
   ordered:="unknown";
fi;
if on_indices then
   # function on indices
   f:=Concatenation("<function_on_indices\n domain=\"",
      domain_info,"\"\n n=\"",String(n),"\"\n");
else 
   # function on the k-subsets of indices
   f:=Concatenation("<function_on_ksubsets_of_indices\n domain_base=\"",
      domain_info,"\"\n n=\"",String(n),"\"\n k=\"",String(k),"\"\n");
fi;
if title<>"" then
   Append(f,Concatenation(" title=\"",title,"\"\n"));
fi;
Append(f,Concatenation(" ordered=\"",String(ordered),"\">\n"));
for i in [1..Length(pre)] do
   Append(f,"<map>\n");
   if pre[i]="blank" then
      Append(f,"<blank/>\n");
   elif IsInt(pre[i]) then
      Append(f,Concatenation("<preimage_cardinality>\n",zXML(pre[i]),
         "</preimage_cardinality>\n"));
   else
      Append(f,"<preimage>\n");
      if Length(pre)=1 then
         Append(f,"<entire_domain/>\n");
      else
         for x in pre[i] do
            if on_indices then
               Append(f,zXML(extrep_index(x)));
            else
               # on k-subsets of indices
               Append(f,"<ksubset>\n");
               for y in x do 
                  Append(f,zXML(extrep_index(y)));
               od;
               Append(f,"</ksubset>\n");
            fi;
         od;
      fi;
      Append(f,"</preimage>\n");
   fi;
   Append(f,Concatenation("<image>\n",zXML(im[i]),"</image>\n"));
   Append(f,"</map>\n");
od;
if on_indices then
   Append(f,"</function_on_indices>\n");
else
   # on k-subsets of indices
   Append(f,"</function_on_ksubsets_of_indices>\n");
fi;
return f;
end);

BindGlobal("point_concurrencesXML",function(D,T,full_preimages)
#
# Given a binary block design D, this function returns the external 
# representation of the t-wise point concurrences. 
# full_preimages is boolean: it is true if we want 
# to record full preimages; false if we want to record 
# preimage cardinalities only. 
#
local t,tsubsetstructure,preimages,concur,title;
if not IsBinaryBlockDesign(D) then
   Error("<D> must be a binary block design");
fi;
if T=[] then
   return "";
fi;
concur:="<point_concurrences>\n";
T:=Set(T);
for t in T do
   tsubsetstructure:=TSubsetStructure(D,t);
   if Length(tsubsetstructure.lambdas)>1 then
      preimages:=tsubsetstructure.partition;
   else
      preimages:=["entire_domain"];
   fi;
   if t=0 then
      title:="number_of_blocks";
   elif t=1 then
      title:="replication_numbers";
   elif t=2 then
      title:="pairwise_point_concurrences";
   else
      title:="";
   fi;
   if not full_preimages then
      if Length(preimages)=1 then
         # entire domain, which is a special case
         preimages:=[NrCombinations([1..D.v],t)];
      else
         preimages:=List(preimages,Length);
      fi; 
   fi;
   Append(concur,
      functionXML("points",D.v,t,title,preimages,tsubsetstructure.lambdas));
od;
Append(concur,"</point_concurrences>\n");
return concur;
end);

BindGlobal("block_concurrencesXML",function(D,T,full_preimages)
#
# Given a binary block design D, this function returns the external 
# representation of the t-wise block concurrences (intersection sizes) 
# of the blocks. 
# full_preimages is boolean: it is true if we want 
# to record full preimages; false if we want to record 
# preimage cardinalities only. 
#
local t,preimages,images,concur,title,extrep_blocks,comb,pos,im;
if not IsBinaryBlockDesign(D) then
   Error("<D> must be a binary block design");
fi;
if T=[] then
   return "";
fi;
extrep_blocks:=extrep_ordered_blocks(D);
concur:="<block_concurrences>\n";
T:=Set(T);
for t in T do
   preimages:=[];
   images:=[];
   if t=0 then
      preimages[1]:=[[]];
      images[1]:=D.v;
   else
      for comb in Combinations([1..Length(extrep_blocks)],t) do
         im:=Length(Intersection(extrep_blocks{comb}));
         pos:=Position(images,im);
         if pos=fail then
            pos:=Length(images)+1;
            preimages[pos]:=[comb];
            images[pos]:=im;
         else
            Add(preimages[pos],comb); # maintaining preimages[pos] as a set
         fi;
      od;
   fi;
   if t=0 then
      title:="number_of_points";
   elif t=1 then
      title:="block_sizes";
   elif t=2 then
      title:="pairwise_block_intersection_sizes";
   else
      title:="";
   fi;
   if not full_preimages then
      preimages:=List(preimages,Length);
   fi;
   Append(concur,
      functionXML("blocks",Length(extrep_blocks),t,title,preimages,images));
od;
Append(concur,"</block_concurrences>\n");
return concur;
end);

BindGlobal("t_design_propertiesXML",function(D)
local t_des,chosen_t,v,b,r,k,all_lambdas,steiner_system,steiner_system_t;
all_lambdas:=AllTDesignLambdas(D);
if Length(all_lambdas)<=2 then
   #  D  is not a t-design with t>=2
   return "";
fi;   
t_des:="<t_design_properties>\n";
chosen_t:=2; # We regard  D  as a (chosen_t)-design. 
             # chosen_t can be any integer from 2 to the maximum t for 
	     # which  D  is a t-design. 
	     # For the time being we fix  chosen_t  to be 2.
v:=D.v;
b:=Length(D.blocks);
r:=all_lambdas[2];
k:=BlockSizes(D)[1];
Append(t_des,Concatenation("<parameters\n",
   " t=\"",String(chosen_t),"\"\n", 
   " v=\"",String(v),"\"\n", 
   " b=\"",String(b),"\"\n", 
   " r=\"",String(r),"\"\n", 
   " k=\"",String(k),"\"\n", 
   " lambda=\"",String(all_lambdas[chosen_t+1]),"\"/>\n"));
Append(t_des,Concatenation("<square flag=\"",String(v=b),"\"/>\n"));
Append(t_des,Concatenation("<projective_plane flag=\"",
   String(v=(k-1)^2+k and all_lambdas[3]=1),"\"/>\n"));
Append(t_des,Concatenation("<affine_plane flag=\"",
   String(v=k^2 and all_lambdas[3]=1),"\"/>\n"));
# We regard  D  as a Steiner system iff it is a t-(v,k,1) for some t>=2.
steiner_system := 1 in all_lambdas{[3..Length(all_lambdas)]};
Append(t_des,Concatenation("<steiner_system flag=\"",
   String(steiner_system),"\""));
if steiner_system then
   steiner_system_t:=Position(all_lambdas{[3..Length(all_lambdas)]},1)+1;
   Append(t_des,Concatenation("\n t=\"",String(steiner_system_t),"\""));
fi;   
Append(t_des,"/>\n");
Append(t_des,Concatenation("<steiner_triple_system flag=\"",
   String(k=3 and all_lambdas[3]=1),"\"/>\n"));
Append(t_des,"</t_design_properties>\n");
return t_des;
end);

BindGlobal("blocksXML",function(D)
local blocks,block,x;
blocks:="<blocks ordered=\"true\">\n";
for block in extrep_ordered_blocks(D) do
   Append(blocks,"<block>\n");
   for x in block do
      Append(blocks,zXML(extrep_index(x)));
   od;
   Append(blocks,"</block>\n");
od;
Append(blocks,"</blocks>\n");
return blocks;
end);

BindGlobal("indicatorsXMLBlockDesign",function(D,include)
local ind,equireplicate,r,constant_blocksize,k,resolvable,mu,
      lambda,no_components,all_lambdas,t_design,maximum_t,
      C,cyclic,one_rotational;
if not ("indicators" in include) then
   return "";
fi;
ind:="<indicators>\n";
Append(ind,Concatenation("<repeated_blocks flag=\"",
   String(not IsSimpleBlockDesign(D)),"\"/>\n"));
constant_blocksize:=Length(BlockSizes(D))=1;
if constant_blocksize then
   k:=BlockSizes(D)[1];
fi;   
r:=ReplicationNumber(D);
equireplicate := r<>fail;
if equireplicate then
   if constant_blocksize and (D.v mod k <> 0) then
      resolvable:=false;
   fi;   
else 
   resolvable:=false;
fi;
if not IsBound(resolvable) and IsBound(D.resolutions) then
   if Length(D.resolutions.list)>0 then
      resolvable:=true;
   elif D.resolutions.allClassesRepresented in [true,"true"] then 
      resolvable:=false;
   fi;
fi;
mu:=AffineResolvableMu(D);
if mu<>fail then
   # D is affine resolvable
   if IsBound(resolvable) and resolvable=false then
      Error("internal error: contradiction in value of <resolvable>");
   fi;
   resolvable:=true;
fi;
if not IsBound(resolvable) then
   if "resolutions" in include then 
      MakeResolutionsComponent(D,2);
      resolvable:=D.resolutions.list<>[];
   elif "resolvable" in include then
      MakeResolutionsComponent(D,0);
      resolvable:=D.resolutions.list<>[];
   fi;
fi;
if IsBound(resolvable) then
   Append(ind,Concatenation("<resolvable flag=\"",String(resolvable),"\"/>\n"));
fi;
Append(ind,Concatenation("<affine_resolvable flag=\"",String(mu<>fail),"\""));
if mu<>fail and mu<>0  then
   Append(ind,Concatenation("\n mu=\"",String(mu),"\""));
fi;   
Append(ind,"/>\n");
Append(ind,Concatenation("<equireplicate flag=\"",
   String(equireplicate),"\""));
if equireplicate then
   Append(ind,Concatenation("\n r=\"",String(r),"\""));
fi;   
Append(ind,"/>\n");
Append(ind,Concatenation("<constant_blocksize flag=\"",
   String(constant_blocksize),"\""));
if constant_blocksize then
   Append(ind,Concatenation("\n k=\"",String(k),"\""));
fi;   
Append(ind,"/>\n");
all_lambdas:=AllTDesignLambdas(D);
t_design:=Length(all_lambdas)>2; # we require t>=2
if t_design then
   maximum_t:=Length(all_lambdas)-1;
fi;   
Append(ind,Concatenation("<t_design flag=\"",String(t_design),"\""));
if t_design then
   Append(ind,Concatenation("\n maximum_t=\"",String(maximum_t),"\""));
fi;   
Append(ind,"/>\n");
no_components:=Length(ConnectedComponents(IncidenceGraphSupportBlockDesign(D)));
Append(ind,Concatenation("<connected flag=\"",String(no_components=1),
   "\"\n no_components=\"",String(no_components),"\"/>\n"));
#
# balance properties 
#
if D.v>1 then
   lambda:=PairwiseBalancedLambda(D);
   Append(ind,Concatenation("<pairwise_balanced flag=\"",
      String(lambda<>fail),"\""));
   if lambda<>fail then
      Append(ind,Concatenation("\n lambda=\"",String(lambda),"\""));
   fi;   
   Append(ind,"/>\n");
   if IsConnectedBlockDesign(D) then 
      Append(ind,Concatenation("<variance_balanced flag=\"",
         String(IsVarianceBalanced(D)),"\"/>\n"));
      if t_design then
         # t>=2, and so our design is pairwise-balanced, as well as 
	 # being binary, connected, equireplicate, and having constant 
	 # block-size.
         Append(ind,"<efficiency_balanced flag=\"true\"/>\n");
      elif Length(all_lambdas)=2 then
         # At this point  D  is binary, connected, equireplicate, and has 
         # constant blocksize, but is not pairwise-balanced.
         Append(ind,"<efficiency_balanced flag=\"false\"/>\n");
      # else *** <efficiency_balanced> t.b.d.
      fi;
   fi;      
fi;   
# # Now we do what can be done easily for <partially_balanced>.
# if t_design then
#    # D is a 2-design
#    Append(ind,"<partially_balanced flag=\"true\"/>\n");
# elif Length(all_lambdas)<2 then
#    # D is not a 1-design, and so is not partially balanced.
#    Append(ind,"<partially_balanced flag=\"false\"/>\n");
# else    
#    # D *is* a 1-design
#    if CC_PermutationGroupProperties(
#          AutGroupBlockDesign(D),D.v).isStratifiable=true then 
#       Append(ind,"<partially_balanced flag=\"true\"/>\n");
#    else
#      # *** for now
#      Append(ind,"<partially_balanced flag=\"unknown\"/>\n");
#    fi;
# fi;   
# *** More needs to done about <efficiency_balanced> and <partially_balanced> 
# *** for arbitrary (binary) block designs.
#
C:=ConjugacyClasses(AutGroupBlockDesign(D));
cyclic:=ForAny(C,x->CycleLengths(Representative(x),[1..D.v])=[D.v]);
one_rotational:=ForAny(C,x->
   SortedList(CycleLengths(Representative(x),[1..D.v]))=[1,D.v-1]);
Append(ind,Concatenation("<cyclic flag=\"",String(cyclic),"\"/>\n"));
Append(ind,Concatenation("<one_rotational flag=\"",
   String(one_rotational),"\"/>\n"));
Append(ind,"</indicators>\n");   
return ind;
end);

BindGlobal("permutation_groupXML",function(G,n,domain,include_properties)
local grp,g,x,C,c,i,pos,cyctypes,cyctypeinfos,flag,prop;
grp:=Concatenation("<permutation_group\n degree=\"",String(n),
   "\"\n order=\"",String(Size(G)),"\"\n domain=\"",domain,"\">\n");
Append(grp,"<generators>\n");
for g in GeneratorsOfGroup(G) do
   Append(grp,permutationXML(g,n));
od;
Append(grp,"</generators>\n");
if include_properties then
   Append(grp,"<permutation_group_properties>\n");
   prop:=CC_PermutationGroupProperties(G,n);
   Append(grp,Concatenation("<primitive flag=\"",
      String(IsPrimitive(G,[1..n])),"\"/>\n"));
   Append(grp,Concatenation("<generously_transitive flag=\"",
      String(prop.isGenerouslyTransitive),"\"/>\n"));
   Append(grp,Concatenation("<multiplicity_free flag=\"",
      String(prop.isMultiplicityFree),"\"/>\n"));
   Append(grp,Concatenation("<stratifiable flag=\"",
      String(prop.isStratifiable),"\"/>\n"));
   Append(grp,Concatenation("<no_orbits value=\"",
      String(Length(Orbits(G,[1..n]))),"\"/>\n"));
   Append(grp,Concatenation("<degree_transitivity value=\"",
      String(Transitivity(G,[1..n])),"\"/>\n"));
   Append(grp,Concatenation("<rank value=\"",
      String(prop.rank),"\"/>\n"));
   # Compute cycle-type information
   C:=ConjugacyClasses(G);
   cyctypes:=[];
   cyctypeinfos:=[];
   for i in [1..Length(C)] do
      c:=SortedList(CycleLengths(Representative(C[i]),[1..n]));
      pos:=Position(cyctypes,c);
      if pos=fail then
         Add(cyctypes,c);
         Add(cyctypeinfos,rec(rep:=Representative(C[i]),count:=Size(C[i])));
      else
         cyctypeinfos[pos].count:=cyctypeinfos[pos].count+Size(C[i]);
      fi;
      SortParallel(cyctypes,cyctypeinfos,extrep_lessthan_onelevel);
   od;
   Append(grp,"<cycle_type_representatives>\n");
   for i in [1..Length(cyctypes)] do
      Append(grp,"<cycle_type_representative>\n");
      Append(grp,permutationXML(cyctypeinfos[i].rep,n));
      Append(grp,"<cycle_type ordered=\"true\">\n");
      for x in cyctypes[i] do
         Append(grp,zXML(x));
      od;
      Append(grp,"</cycle_type>\n");
      Append(grp,"<no_having_cycle_type>\n");
      Append(grp,zXML(cyctypeinfos[i].count));
      Append(grp,"</no_having_cycle_type>\n");
      Append(grp,"</cycle_type_representative>\n");
   od;   
   Append(grp,"</cycle_type_representatives>\n");
   Append(grp,"</permutation_group_properties>\n");
fi;
Append(grp,"</permutation_group>\n");
return grp;
end);

BindGlobal("automorphism_groupXMLBlockDesign",function(D,include)
local aut,A,value,flag;
if not ("automorphism_group" in include) then
   return "";
fi;
A:=AutGroupBlockDesign(D);
aut:="<automorphism_group>\n"; 
Append(aut,permutation_groupXML(A,D.v,"points",true));
Append(aut,"<automorphism_group_properties>\n");
if IsSimpleBlockDesign(D) then
   flag:=String(IsPrimitive(A,D.blocks,OnSets)); 
else
   flag:="not_applicable";
fi;   
Append(aut,Concatenation("<block_primitive flag=\"",flag,"\"/>\n"));
if IsSimpleBlockDesign(D) then
   value:=String(Length(Orbits(A,D.blocks,OnSets))); 
else
   value:="not_applicable";
fi;   
Append(aut,Concatenation("<no_block_orbits value=\"",value,"\"/>\n"));
if IsSimpleBlockDesign(D) then
   value:=String(Transitivity(A,D.blocks,OnSets)); 
else
   value:="not_applicable";
fi;   
Append(aut,Concatenation("<degree_block_transitivity value=\"",value,"\"/>\n"));
Append(aut,"</automorphism_group_properties>\n");
Append(aut,"</automorphism_group>\n");
return aut;
end);

BindGlobal("resolutionsXML",function(D,include)
local res,R,S,s,extrep_blocks,A,aut,g,Rlist,allrep,noniso;
if not ("resolvable" in include) and not ("available_resolutions" in include) 
   and not ("resolutions" in include) then
   return "";
fi;
if "resolutions" in include then 
   if not IsBound(D.resolutions) or 
      not (D.resolutions.allClassesRepresented in [true,"true"]) or
      not (D.resolutions.pairwiseNonisomorphic in [true,"true"]) then
      MakeResolutionsComponent(D,2);
   fi;
elif "resolvable" in include then
   if not IsBound(D.resolutions) or (D.resolutions.list=[] 
         and not (D.resolutions.allClassesRepresented in [true,"true"])) then
      MakeResolutionsComponent(D,0);
   fi;
fi;
if not IsBound(D.resolutions) or D.resolutions.list=[] then
   return "";
fi;
if "resolutions" in include then
   Rlist:=D.resolutions.list;
   allrep:=true;
   noniso:=true;
elif "available_resolutions" in include then
   Rlist:=D.resolutions.list;
   if D.resolutions.allClassesRepresented in [true,"true"] then
      allrep:=true;      
   else
      allrep:="unknown";
   fi;   
   if D.resolutions.pairwiseNonisomorphic in [true,"true"] then
      noniso:=true;      
   else
      noniso:="unknown";
   fi;   
else
   # we shall write out just one resolution
   Rlist:=[D.resolutions.list[1]];
   if Length(D.resolutions.list)=1 and 
      (D.resolutions.allClassesRepresented in [true,"true"]) then
      allrep:=true;      
   else
      allrep:="unknown";
   fi;   
   noniso:=true;
fi;   
res:=Concatenation("<resolutions\n pairwise_nonisomorphic=\"",String(noniso),"\"",
   "\n all_classes_represented=\"",String(allrep),"\">\n");
for R in Rlist do 
   Append(res,"<resolution>\n");
   extrep_blocks:=extrep_ordered_blocks(D);
   S:=List(R.partition,x->List(x.blocks,
      y->PositionSorted(extrep_blocks,y,extrep_lessthan_onelevel)));
   for s in S do
      Sort(s);
   od;
   Sort(S,extrep_lessthan_onelevel);
   Append(res,functionXML("blocks",Length(D.blocks),"","resolution",S,
      List([1..Length(S)],extrep_index)));
   if IsBound(R.autGroup) then
      A:=R.autGroup;
      aut:="<automorphism_group>\n"; 
      Append(aut,permutation_groupXML(A,D.v,"points",false)); 
      Append(aut,"</automorphism_group>\n");
      Append(res,aut);
   fi;
   Append(res,"</resolution>\n");
od;
Append(res,"</resolutions>\n");
return res;
end);

BindGlobal("combinatorial_propertiesXML",function(D,include)
local comb,i,r,alpha;
if not ("combinatorial_properties" in include) then
   return "";
fi;
comb:="<combinatorial_properties>\n";
if D.v=1 then
   # special case
   Append(comb,point_concurrencesXML(D,[1],true));
elif Length(AllTDesignLambdas(D))>2 then
   # D is a t-design with t>=2
   Append(comb,
      point_concurrencesXML(D,[1..Length(AllTDesignLambdas(D))-1],true));
else
   Append(comb,point_concurrencesXML(D,[1,2],true));
fi;
if Length(D.blocks)=1 then
   # special case
   Append(comb,block_concurrencesXML(D,[1],false));
else    
   Append(comb,block_concurrencesXML(D,[1,2],false));
fi;   
Append(comb,t_design_propertiesXML(D));
#
r:=ReplicationNumber(D);
if r<>fail then
   # D is equireplicate
   Append(comb,"<alpha_resolvable>\n");
   if IsBound(D.resolutions) and Length(D.resolutions.list)>0 then
      # D  is 1-resolvable, and so  D  is alpha-resolvable for all
      # divisors  alpha  of  r.
      for alpha in DivisorsInt(r) do
         Append(comb,index_flagXML(alpha,true));
      od;	 
   else 	 
      # for now
      Append(comb,index_flagXML(r,true));
   fi;   
   Append(comb,"</alpha_resolvable>\n");
fi;
# *** alpha resolvability needs to be worked on further.
#
Append(comb,"<t_wise_balanced>\n");
if D.v=1 then
   # special case
   Append(comb,index_flagXML(1,true));
elif Length(AllTDesignLambdas(D))>2 then
   # D is a t-design with t>=2
   for i in [1..Length(AllTDesignLambdas(D))-1] do
      Append(comb,index_flagXML(i,true));
   od;
else
   Append(comb,index_flagXML(1,ReplicationNumber(D)<>fail));
   Append(comb,index_flagXML(2,PairwiseBalancedLambda(D)<>fail));
fi;
Append(comb,"</t_wise_balanced>\n");
Append(comb,"</combinatorial_properties>\n");
return comb;
end);

BindGlobal("block_designXML",function(D,include,id)
#
# Returns the binary block design  D  in external representation format. 
#
# The list  include  gives information on the combinatorial and 
# group-theoretical information to be exanded for  D.
# This list should contain zero or more of the strings
# "indicators", "resolvable", "combinatorial_properties", 
# "automorphism_group", and "resolutions".  
#
#  id  is used as the id of the output design. 
#
local block_design;
if not IsBinaryBlockDesign(D) then
   Error("<D> must be a binary block design");
fi;
block_design:=Concatenation("<block_design\n id=\"", id, "\"\n",
    " v=\"", String(D.v), "\"\n b=\"", String(Length(D.blocks)), "\">\n");
Append(block_design,blocksXML(D));
if IsBound(D.point_labelsXML) then
   Append(block_design,D.point_labelsXML);
   # How to handle D.pointNames (if bound) ???
fi;   
Append(block_design,indicatorsXMLBlockDesign(D,include));
Append(block_design,combinatorial_propertiesXML(D,include));
Append(block_design,automorphism_groupXMLBlockDesign(D,include));
Append(block_design,resolutionsXML(D,include));
if IsBound(D.statistical_propertiesXML) then
   Append(block_design,D.statistical_propertiesXML);
fi;   
if IsBound(D.alternative_representationsXML) then
   Append(block_design,D.alternative_representationsXML);
fi;   
if IsBound(D.infoXML) then
   Append(block_design,D.infoXML);
fi;   
Append(block_design,"</block_design>\n");
return block_design;
end);

BindGlobal("BlockDesignsToXMLFile",function(arg) 
#
# Let filename = arg[1],  designs = arg[2].
# Then  filename  is a string and  designs  a list or record.
# If  designs  is a list then  designs := rec(list:=designs). 
#
# This function expands and writes the external representation of the list 
# designs.list  of (assumed distinct) binary block designs to the file 
# with name  filename.
# 
# designs.pairwiseNonisomorphic (if bound) should be `true' or `false' or 
#    the string "unknown", specifying the pairwise-nonisomorphism status
#    of the designs in  designs.list.
# designs.infoXML (if bound) should contain a string in XML format for
#    the info element of the list_of_designs which is written.
#
# The information output for each design depends on  include = arg[3],  
# which should be the string "all" or a list containing zero or more of 
# the strings "indicators", "resolvable", "combinatorial_properties", 
# "automorphism_group", "resolutions",  and "available_resolutions".
# A shorthand for a list implying all these strings 
# is "all". The default for  include  is  [].
#
# If  list_id = arg[4]  is bound, then the id's of the output designs
# will be  "list_id-0", "list_id-1", "list_id-2", ...
#
local filename,designs,include,list_id,i,stream,design_id;
filename:=arg[1];
designs:=arg[2];
if IsList(designs) then
   designs:=rec(list:=designs);
fi;   
if not IsString(filename) or not IsRecord(designs) then
   Error("usage: BlockDesignsToXMLFile( <String>, <List> or <Record> [, ... ] )");
fi;   
designs:=ShallowCopy(designs);
if not IsBound(designs.pairwiseNonisomorphic) then
   if Length(designs.list)<2 then
      designs.pairwiseNonisomorphic:=true;
   else   
      designs.pairwiseNonisomorphic:="unknown";
   fi;
fi;   
if not IsBound(designs.infoXML) then
   designs.infoXML:=infoXML(
      [Concatenation("[ DESIGN-",InstalledPackageVersion("design"),
                     ", GRAPE-",InstalledPackageVersion("grape"),
                     ", GAPDoc-",InstalledPackageVersion("gapdoc"),
                     ", GAP-",GAPInfo.Version," ]")],
      [],[]);
fi;   
if IsBound(arg[3]) then 
   if not IsList(arg[3]) then
      Error("<arg[3]> must be a list or the string \"all\"");
   fi;
   if arg[3]="all" then
      include:=["indicators","resolvable","combinatorial_properties", 
                "automorphism_group","resolutions"]; 
   else
      include:=arg[3];
   fi;
   if not IsSubset(["indicators","resolvable","combinatorial_properties", 
             "automorphism_group","resolutions","available_resolutions"], 
             include) then
      Error("<include> contains an invalid option");
   fi;
else
   include:=[];
fi;
if IsBound(arg[4]) then
   if not IsString(arg[4]) then
      Error("<arg[4]> must be a string");
   fi;
   list_id:=arg[4];
fi;
stream:=OutputTextFile(filename,false);
SetPrintFormattingStatus(stream,false);
PrintTo(stream,"<?xml version=\"1.0\"?>\n"); # starts file with XML header
AppendTo(stream,"<list_of_designs\n",
   " xmlns=\"http://designtheory.org/xml-namespace\"\n",
   " dtrs_protocol=\"",DESIGN_DTRS_PROTOCOL,"\"\n",
   " design_type=\"block_design\"\n",
   " pairwise_nonisomorphic=\"",String(designs.pairwiseNonisomorphic),"\"\n",
   " no_designs=\"",String(Length(designs.list)),"\">\n");
if designs.infoXML<>"" then
   AppendTo(stream,designs.infoXML);
fi;
AppendTo(stream,"<designs>\n"); 
for i in [1..Length(designs.list)] do 
   if IsBound(list_id) then
      design_id:=Concatenation(list_id,"-",String(extrep_index(i)));
   elif IsBound(designs.list[i].id) then
      design_id:=designs.list[i].id;
   else
      design_id:=Concatenation("design-",String(extrep_index(i)));
   fi;
   AppendTo(stream,block_designXML(designs.list[i],include,design_id));
od;
AppendTo(stream,"</designs>\n"); 
AppendTo(stream,"</list_of_designs>\n");
CloseStream(stream);
end);

#
# The Reader
#

BindGlobal("gap_index",function(i)
#
# Returns the GAP index corresponding to the external representation index i.
#
return i+1;
end);

BindGlobal("IntFromParseTree",function(T)
local n,t,ch,C;
n:="";
C:=Set(['-','0','1','2','3','4','5','6','7','8','9']); 
for t in T.content do
   if t.name<>"PCDATA" then
      Error("should be PCDATA here");
   fi;   
   for ch in t.content do
      if ch in C then
         Add(n,ch);
      fi;
   od;
od;   
n:=Int(n);
return n;
end);

BindGlobal("BlockFromParseTree",function(T)
local t,block;
block:=[];
for t in T.content do 
   if t.name="z" or t.name="n" then
      Add(block,gap_index(IntFromParseTree(t)));
   fi;
od;
return AsSortedList(block);
end);

BindGlobal("BlocksFromParseTree",function(T)
local t,blocks;
blocks:=[];
for t in T.content do 
   if t.name="block" then
      Add(blocks,BlockFromParseTree(t));
   fi;
od;
return AsSortedList(blocks);
end);

BindGlobal("BlockDesignFromParseTree",function(T,str)
#
# Converts the (binary) block design in the parse tree <T> to
# a block design in GAP Design package format, and returns the
# result. 
#
local t,D;
D:=rec(isBlockDesign:=true, v:=Int(T.attributes.v));
D.id:=Immutable(T.attributes.id);
for t in T.content do 
   if t.name="blocks" then
      D.blocks:=Immutable(BlocksFromParseTree(t));
   elif t.name="point_labels" then
      D.point_labelsXML:=Immutable(str{[t.start..t.stop]});
   elif t.name="statistical_properties" then
      D.statistical_propertiesXML:=Immutable(str{[t.start..t.stop]});
   elif t.name="alternative_representations" then   
      D.alternative_representationsXML:=Immutable(str{[t.start..t.stop]});
   elif t.name="info" then   
      D.infoXML:=Immutable(str{[t.start..t.stop]});
   fi;
od;
BlockDesign(D.v,D.blocks); # does some checks
if not IsBinaryBlockDesign(D) then
   Error("<D> must be a binary block design");
fi;
return D;
end);

BindGlobal("BlockDesignsFromXMLFile",function(filename)
#
# This function reads a file with name <filename>, containing a 
# list of (binary) block designs in external representation XML-format,
# and returns a record  <designs> in GAP format containing the essential
# information in this file.
#
# The record <designs> contains the following components:
#    `list': a list of block designs in GAP Design package format of 
#       the list of block designs in the file; 
#    `pairwiseNonisomorphic':
#       is set appropriately according to the attribute pairwise_nonisomorphic:
#       `false' if this attribute = "false",
#       `true' if this attribute = "true",
#       "unknown" otherwise;
#    `infoXML': 
#       bound iff the  info  element occurs in the XML 
#       list_of_designs  element, and if bound, contains
#       this  info  element in a string.
#
local T,t,u,str,designs;
if not IsString(filename) then
   Error("usage: BlockDesignsFromXMLFile( <String> );");
fi;
str:=StringFile(filename);
T:=ParseTreeXMLString(str);
if T.name<>"list_of_designs" then
   for t in T.content do 
      if t.name="list_of_designs" then
         T:=t;
         break;
      fi;
   od;
fi;
if T.attributes.design_type<>"block_design" then
   Error("the design_type must be block_design");
fi;   
designs:=rec(list:=[]);
if T.attributes.pairwise_nonisomorphic="false" then
   designs.pairwiseNonisomorphic:=false;
elif T.attributes.pairwise_nonisomorphic="true" then
   designs.pairwiseNonisomorphic:=true;
else 
   designs.pairwiseNonisomorphic:="unknown";
fi;
for t in T.content do
   if t.name="info" then
      designs.infoXML:=Immutable(str{[t.start..t.stop]});
   elif t.name="designs" then
      for u in t.content do
         if u.name="block_design" then 
            Add(designs.list,BlockDesignFromParseTree(u,str));
	 fi;
      od;
   elif t.name="block_design" then # for dtrs protocol 1.x compatibility
      Add(designs.list,BlockDesignFromParseTree(t,str));
   fi;
od;
return designs;
end);

