XMLgenMC_ver="0.28";
(* ver 0.28 Parent Category Changed *)
(* ver 0.27 Bug fixed (GenMCxmlAns) *)
(* ver 0.26 DebPrint[a_] added *)
(* ver 0.25 IfDeb[a_] added *)
(* ver 0.24 IfDeb,DebOn,DebOff are officially supported *)
(* ver 0.23 Divide GCD of each row (ZSweepOut1) *)
(* ver 0.22 ZSweepOut1, ZSweepOutB1 added *)
(* ver 0.21 DFname[no_]: added *)
(* ver 0.20 New Scheme *)
(* ver 0.11 Shuffle Answers even if 'shuffle' option is 'false' *)
(* ver 0.10 No shuffle options added *)
(* ver 0.09 Col, ColCat, RowCat added *)
(* ver 0.08 2019.04.17 Global variables: GeneralFeedback etc. added *)
(* ver 0.07 2019.04.16 Global variables: CorrectFeedback,Inc.., Par... etc. added *)
(* ver 0.06 2019.04.14 Global variables: Numbering added *)
(* ver 0.05 2018.10.11 Matrix Generators are added from FindHomeEq *)
(* ver 0.04 2018.10.02 OneLineTeXForm[],TTS[] added *)
(* ver 0.03 2018.09.28 DFname[] Added *)
(* Output fine name / change if needed / cf. AbsoluteTime[] *)

(* UnsortedUnion[x_]:=Module[{f},f[y_]:=(f[y]=Sequence[];y);f/@x] *)

(* Supporting functions and variables *)
RandomNonZero[m_]:=(RandomInteger[m]+1)*(2*RandomInteger[1]-1);
Rnz[m_]:=RandomNonZero[m];(* \\pm 1,\pm2,...,\pm(m+1) *)
Rpos[m_]:=RandomInteger[m]+1; (* 1,2,...,m+1 *)
Rbtw[m_]:=RandomInteger[2m]-m;
PrintFd[n_,d_]:=Module[{i,tmp0=""},For[i=d-1,i≥0,i--,
If[n>=10^i,Return[tmp0<>ToString[n]],tmp0=tmp0<>"0"]]];
OneLineTeXForm[exp_]:=Module[{tmp,p},tmp=Characters[ToString[TeXForm[exp]]];p=Position[tmp,"\n"];StringJoin[Delete[tmp,p]]];
TT[ff_]:=ToString[TeXForm[ff]];
TTStd[ff_]:=ToString[TeXForm[StandardForm[ff]]];
TTT[mm_]:=ToString[TeXForm[Transpose[mm]]];
TOT[mt_]:=ToString[OneLineTeXForm[mt]];
TOTT[mt_]:=ToString[OneLineTeXForm[Transpose[mt]]];
TTdf[r_]:=StringReplace[TT[r],"\\frac"->"\\dfrac"];
DFname[]:=Module[{DF},DF=StringReplace[NotebookFileName[],{NotebookDirectory[]->"",".nb"->""}];
DF=DF<>"_"<>StringTake[ToString[DateValue["Year"]],{3,4}];
DF=DF<>ToString[PrintFd[DateValue["Month"],2]]<>ToString[PrintFd[DateValue["Day"],2]];
DF=DF<>".xml";Return[DF]];
DFname[no_]:=Module[{DF,noS},DF=StringReplace[NotebookFileName[],{NotebookDirectory[]->"",".nb"->""}];
noS=PrintFd[no,3];DF=DF<>"_"<>noS;DF=DF<>".xml";Return[DF]];
FuncList={TT,TTS,TTT,RndNZ,PrintFd,OneLineTeXForm,DFname};

(* Gloabl Variable *)
PERLIST={"100","50","33.33333","25","20",
"16.66666","14.28571","12.5","11.11111","10"};
AnswerTemplate=StringJoin[{
"      <answer fraction=\"__Per__\" format=\"html\">\n",
"        <text><![CDATA[<p>__Pat__</p>]]></text>\n",
"      <feedback format=\"html\">\n",
"        <text><![CDATA[<p>__Fdb__</p>]]></text>\n",
"      </feedback>\n",
"      </answer>\n"}];
(* no numbering default *)
Numbering="none"; (* or of "123" , "abc" , "ABC" , "iii" , "III" *)
Shuffle="true"; 
GeneralFeedback="";
CorrectFeedback="";
PartiallyCorrectFeedback="";
IncorrectFeedback="";
DebugMode=True;
DebOn[]:=DebugMode=True;DebOff[]:=DebugMode=False;
IfDeb[a_,b_]:=If[DebugMode,a,b];IfDeb[a_]:=If[DebugMode,a];

GenMCxmlHead[Title_,Qhtml_,SingleTF_]:=StringJoin[{
"  <!-- question: "<>ToString[RandomInteger[9000000]+1000000]<>" -->\n",
"  <question type=\"multichoice\">\n",
"    <name>\n      <text>"<>Title<>"</text>\n    </name>\n",
"    <questiontext format=\"html\">\n",
"      <text><![CDATA[<p>"<> Qhtml<>"</p>]]></text>\n",
"    </questiontext>\n",
"    <generalfeedback format=\"html\">\n",
"      <text>"<>GeneralFeedback<>"</text>\n    </generalfeedback>\n",
"    <defaultgrade>1.0000000</defaultgrade>\n",
"    <penalty>0.3333333</penalty>\n    <hidden>0</hidden>\n",
"    <single>"<> SingleTF<>"</single>\n",
"    <shuffleanswers>"<>Shuffle<>"</shuffleanswers>\n",(* Shuffle default true *)
"    <answernumbering>"<>Numbering<>"</answernumbering>\n",
"    <correctfeedback format=\"html\">\n",
"      <text>"<>CorrectFeedback<>"</text>\n    </correctfeedback>\n",
"    <partiallycorrectfeedback format=\"html\">\n",
"      <text>"<>PartiallyCorrectFeedback,
"</text>\n    </partiallycorrectfeedback>\n",
"    <incorrectfeedback format=\"html\">\n",
"      <text>"<>IncorrectFeedback<>"</text>\n    </incorrectfeedback>\n",
"    <shownumcorrect/>\n"}];

(* AnsList = {{Cn,In},{"C","Ans1","Fdb1"},{"I","Ans2","Fdb2"},...} *)
GenMCxmlAns[AnsList_,SingleTF_]:=Module[{AnsL={},Ans1,ct,Cn,In,per,Cper,Iper},
{Cn,In}=AnsList[[1]];
(* For[ct=2,ct≤Length[AnsList],ct++,If[AnsList[[ct,1]]=="C",Cn=Cn+1,In=In+1]]; *)
If[SingleTF=="true",Cper=PERLIST[[1]],Cper=PERLIST[[Cn]]];Iper="-"<>PERLIST[[2]];
For[ct=2,ct≤Length[AnsList],ct++,
If[AnsList[[ct,1]]=="C",per=Cper,per=Iper];
AnsL=Append[AnsL,StringReplace[AnswerTemplate,
{"__Per__"->per,"__Pat__"->AnsList[[ct,2]],"__Fdb__"->AnsList[[ct,3]]}]]];
Return[StringJoin[AnsL]]];
FuncList=Append[FuncList,GenMCxmlAns];

GenerateMCxml1[Title_,SingleTF_,Qhtml_,CorrectList_,IncorrectList_,IncorrectPer_]:=
Module[{CORRECTPER,INCORRECTPER,ct,Cnum=Length[CorrectList],
ICnum=Length[IncorrectList],AnsL,Per,Pat,Fdb,CIL,CILlen,Ret},
(* If[Cnum==1,SingleTF="true",SingleTF=Single]; *)
If[SingleTF=="true",CORRECTPER=PERLIST[[1]],CORRECTPER=PERLIST[[Cnum]]];
INCORRECTPER="-"<>PERLIST[[2]]; (* always -50 *)
(* Print["Debug: Cnum=",Cnum,", ICnum=",ICnum];*)
Ret=GenMCxmlHead[Title,Qhtml,SingleTF];
AnsL={};For[ct=1,ct≤Cnum+ICnum,ct++,
If[ct≤Cnum,Per=CORRECTPER;CIL=CorrectList[[ct]],
Per=INCORRECTPER;CIL=IncorrectList[[ct-Cnum]]];
If[ListQ[CIL],Pat=CIL[[1]];Fdb=CIL[[2]],Pat=CIL;Fdb=""];
AnsL=Append[AnsL,StringReplace[AnswerTemplate,{
"__Per__"->Per,"__Pat__"->Pat,"__Fdb__"->Fdb}]]];
Ret=Ret<>StringJoin[AnsL];
Ret=Ret<>"  </question>\n\n";
Return[Ret]];FuncList=Append[FuncList,GenerateMCxml1];

ConvertAnsList[CL_,ICL_]:=Module[{AList={},Ans1,ct},
For[ct=1,ct≤Length[CL],ct++,
Ans1={"C"};If[ListQ[CL[[ct]]],Ans1=Join[Ans1,CL[[ct]]],Ans1=Join[Ans1,{CL[[ct]],""}]];
AList=Append[AList,Ans1]];
For[ct=1,ct≤Length[ICL],ct++,
Ans1={"I"};If[ListQ[ICL[[ct]]],Ans1=Join[Ans1,ICL[[ct]]],Ans1=Join[Ans1,{ICL[[ct]],""}]];
AList=Append[AList,Ans1]];
Return[AList]];

GenerateMCxml[Fname_(* save filename *),Cat_(* category string *),Title_(* title string (auto numbering) *),
Num_(* Number of questions *),Single_(* "true" or "false" *),
Qtext_(* Question text string or Generate Qtext function *),
CListFunc_(* Function name which returns {Correct Answer1,CA2,...} or {{CA1,Feedback1},{CA2,Fdb2},...}s *),
ICListFunc_(* Function name which returns {Incorrect Answer1,IA2,...} or {{IA1,Fdb1},{IA2,Fdb2},...}s *)]:=
Module[{mm,dt,mmS,TitleN,XMLstring="",Qtext1,CList,ICList},
dt=Floor[N[Log[Num]/Log[10],2]]+1;
XMLstring=StringJoin[{
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n","<quiz>\n",
"<!-- question: 0  -->\n","  <question type=\"category\">\n",
"    <category>\n","        <text>",Cat,
"</text>\n","    </category>\n","  </question>"}];
For[mm=1,mm≤Num,mm++,mmS=PrintFd[mm,dt];TitleN=Title <>"("<>mmS<>")";
If[StringQ[Qtext],Qtext1=Qtext,Qtext1=Qtext[mm]];
CList=CListFunc[mm];ICList=ICListFunc[mm];
XMLstring=XMLstring<>GenerateMCxml1[TitleN,Single,Qtext1,CList,ICList,"50"]];XMLstring=XMLstring<>"</quiz>\n";
SetDirectory[NotebookDirectory[]];
If[DebugMode,Print[XMLstring],Export[Fname,XMLstring,"Text"]]];FuncList=Append[FuncList,GenerateMCxml];

GenMCxmlV2[Fname_,Cat_,Title_,Num_,SingleTF_,Qtext_,AnsListFunc_]:=
Module[{mm,dt,mmS,TitleN,XMLstring="",XML1,Qtext1},
dt=Floor[N[Log[Num]/Log[10],2]]+1;
XMLstring=StringJoin[{
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n","<quiz>\n",
"<!-- question: 0  -->\n","  <question type=\"category\">\n",
"    <category>\n","        <text>",Cat,
"</text>\n","    </category>\n","  </question>"}];
For[mm=1,mm≤Num,mm++,mmS=PrintFd[mm,dt];TitleN=Title <>"("<>mmS<>")";
If[StringQ[Qtext],Qtext1=Qtext,Qtext1=Qtext[mm]];
XML1=StringJoin[{GenMCxmlHead[TitleN,Qtext1,SingleTF],
GenMCxmlAns[AnsListFunc[mm],SingleTF],"  </question>\n\n"}];
XMLstring=XMLstring<>XML1];
XMLstring=XMLstring<>"</quiz>\n";
SetDirectory[NotebookDirectory[]];
If[DebugMode,Print[XMLstring],
 Export[Fname,XMLstring,"Text"]]];FuncList=Append[FuncList,GenMCxmlV2];

(* Some common library related to matrices *)

Col[A_,n_]:=Transpose[{A[[Range[Length[A]],n]]}];
ColCat[CL_]:=Join@@@(Transpose@CL);
RowCat[CL_]:=Join@@(CL);
GCDL[ZL_]:=Module[{ct,ret=0},For[ct=1,ct≤Length[ZL],ct++,ret=GCD[ret,ZL[[ct]]]];Return[ret]];

GetPnijc[n_,i_,j_,c_]:=Module[{Ret,row,col},Ret=Table[Table[If[row==col,1,0],{row,1,n}],{col,1,n}];
Ret[[i,j]]=c;Return[Ret]];
GetQnic[n_,i_,c_]:=Module[{Ret,row,col},Ret=Table[Table[If[row==col,1,0],{row,1,n}],{col,1,n}];Ret[[i,i]]=c;Return[Ret]];
GetRnij[n_,i_,j_]:=Module[{Ret,row,col},Ret=Table[Table[If[row==col,1,0],{row,1,n}],{col,1,n}];
{Ret[[i,i]],Ret[[i,j]],Ret[[j,i]],Ret[[j,j]]}={0,1,1,0};Return[Ret]];
RandomEchelonPV[n_,m_,pv_,range_]:=Module[{RetMat,i,j,r,k},r=Length[pv];
RetMat=Table[Table[If[i>r,0,If[j<pv[[i]],0,If[j==pv[[i]],1,RandomInteger[range]]]],{j,1,m}],{i,1,n}];
If[r>1,For[i=2,i≤r,i++,j=pv[[i]];For[k=1,k<i,k++,RetMat[[k,j]]=0]]];
Return[RetMat]];
(* RandomEchelonPV2 : range is a list of parameters *)
RandomEchelonPV2[n_,m_,pv_,range_]:=Module[{RetMat,i,j,r,k},r=Length[pv];
RetMat=Table[Table[If[i>r,0,If[j<pv[[i]],0,If[j==pv[[i]],1,RandomChoice[range]]]],{j,1,m}],{i,1,n}];
If[r>1,For[i=2,i≤r,i++,j=pv[[i]];For[k=1,k<i,k++,RetMat[[k,j]]=0]]];
Return[RetMat]];
RandomEchelonRank[n_,m_,r_,range_]:=Module[{RetMat,pv,i,j,k},
pv=Sort[RandomSample[Table[(m+1-i)^1.4,{i,1,m}]->Range[m],r]];Return[RandomEchelonPV[n,m,pv,range]]];
ChkMatrix[P_]:=Module[{zct=0,ijmax=0,ijsum=0,ijabssum=0,i,j,n},n=Length[P];
For[i=1,i≤n,i++,For[j=1,j≤n,j++,If[P[[i,j]]==0,++zct];
ijmax=Max[ijmax,Abs[P[[i]][[j]]]];
ijsum=ijsum+P[[i]][[j]];
ijabssum=ijabssum+Abs[P[[i]][[j]]]]];
{zct,ijmax,ijsum,ijabssum}];
ZSweepOut1[MT_,j_,rs_]:=Module[{EMP,MT1,Cj,Cjr,g,row,q,ct,mi,miv},row=Length[MT];
EMP=IdentityMatrix[row];MT1=MT;
Cj=Flatten[Col[MT1,j]];If[row<rs,Return[{MT1,EMP}]];
Cjr=Cj[[Range[rs,row]]];miv=Max[Abs[Cjr]];
If[miv==0,Return[{MT1,EMP}]];
For[ct=rs,ct≤row,ct++,
If[Cj[[ct]]≠0 && Abs[Cj[[ct]]]<miv,miv=Abs[Cj[[ct]]]]];
For[ct=rs,ct≤row,ct++,
If[Abs[Cj[[ct]]]==miv,mi=ct;Break[]]];
If[Cj[[mi]]<0,EMP=GetQnic[row,mi,-1].EMP;MT1=GetQnic[row,mi,-1].MT1];
If[mi>rs,EMP=GetRnij[row,rs,mi].EMP;MT1=GetRnij[row,rs,mi].MT1];
Cj=Flatten[Col[MT1,j]];miv=Cj[[rs]];mi=rs;
For[ct=1,ct≤row,ct++,
If[ct≠mi&&Cj[[ct]]≠0,q=Quotient[Cj[[ct]],miv];
EMP=GetPnijc[row,ct,mi,-q].EMP;MT1=GetPnijc[row,ct,mi,-q].MT1];
g=GCDL[MT1[[ct]]];If[g>1,MT1[[ct]]=MT1[[ct]]/g;EMP=GetQnic[row,ct,1/g]]];
Return[{MT1,EMP}]];
ZSweepOutB1[MT_,j_,rs_]:=Module[{EM1,MT1,Cj,Cjr,row,q,ct,mi,miv},row=Length[MT];
EM1=IdentityMatrix[row];MT1=MT;
Cj=Flatten[Col[MT1,j]];If[row<rs,Return[{MT1,EM1}]];
Cjr=Cj[[Range[rs,row]]];miv=Max[Abs[Cjr]];
If[miv==0,Return[{MT1,EM1}]];
For[ct=rs,ct≤row,ct++,
If[Cj[[ct]]≠0 && Abs[Cj[[ct]]]<miv,miv=Abs[Cj[[ct]]]]];
For[ct=rs,ct≤row,ct++,
If[Abs[Cj[[ct]]]==miv,mi=ct;Break[]]];
If[Cj[[mi]]<0,EM1=GetQnic[row,mi,-1].EM1;MT1=GetQnic[row,mi,-1].MT1];
If[mi>rs,EM1=GetRnij[row,rs,mi].EM1;MT1=GetRnij[row,rs,mi].MT1];
Cj=Flatten[Col[MT1,j]];miv=Cj[[rs]];
For[ct=rs+1,ct≤row,ct++,
If[Cj[[ct]]≠0,q=Quotient[Cj[[ct]],miv];
EM1=GetPnijc[row,ct,mi,-q].EM1;MT1=GetPnijc[row,ct,mi,-q].MT1]];
Return[{MT1,EM1}]];
FuncList=Join[FuncList,{GetPnijc,GetQnic,GetRnij,RandomEchelonPV,RandomEchelonRank,ChkMatrix}];
FuncList=Join[FuncList,{ZSweepOut1,ZSweepOutB1}];
(* GenerateUT1 : upper triangle matirx with diagonal (1,1,...,1), t: max, q: random control para, d0: max of total of abs val of upper entries *)
RandomUT1[n_,t_,q_,d0_]:=RandomUTM[n,t,q,d0,Table[1,{i,1,n}]];
RandomUTM[n_,t_,q_,d0_,diag_]:=Module[{i,j,r,PU,d=Floor[d0]},
PU=Table[Table[If[i==j,diag[[i]],0],{j,1,n}],{i,1,n}];
For[i=1,i≤n-1,i++,For[j=i+1,j≤n,j++,
r=(2*RandomInteger[1]-1)*Floor[(RandomInteger[Max[1,t-1]]+1)^q-0.5];
PU[[i]][[j]]=r;
d=d-Abs[r];If[d<=0,Break[]]]];
Return[PU]];
RandomLT1[n_,t_,q_,d0_]:=Transpose[RandomUT1[n,t,q,d0]];
RandomLTM[n_,t_,q_,d0_,diag_]:=Transpose[RandomUTM[n,t,q,d0,diag]];
(* RandomMatrixDetOne: Random matrix of determinant 1 *)
(* n:deg, zct1,zct2:min,max of # of 0, max: max of ent, *)
(* ijsum: max of sum of ent, ijasum: max of sum of abs of ent *)
RandomMatrixDetOne[n_,zct1_,zct2_,max_,ijsum_,ijasum_]:=Module[{M,ct,rd=0,z,m,s,a},
Label[Redo];
If[rd>500,Print["maximal redo count detected. abort"];Interrupt[]];
M=RandomUT1[n,max,1/3,n*(n-1)];
For[ct=1,ct<20,ct++,{z,m,s,a}=ChkMatrix[M];
If [z>=zct1&&z≤zct2 && m<=max &&s<=ijsum && a<=ijasum,Break[]];
M=RandomUT1[n,max,1/3,n*(n-1)/4].RandomLT1[n,max,1/3,n*(n-1)/4].M];
If[ct≥20,rd=rd+1;Goto[Redo]];
Return[M]];
FuncList=Join[FuncList,{RandomUT1,RandomUTM,RandomLT1,RandomLTM,RandomMatrixDetOne}];

SetDirectory[NotebookDirectory[]];
DumpSave["XMLgenMC.mx","Global`"];