Missing each in Mac case of Files.List

Missing each in Mac case of Files.List

:Namespace Files

⍝ Provides cover functions for many file operations
⍝ Eventually all will be implemented for both Windows and *nix

    (⎕IO ⎕ML)←1

    ∇ r←text AppendText name;tn
     ⍝ Append text to existing file (must be single byte text)
      tn←name ⎕NTIE 0
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ (tn name)←{ext}CreateTemp folder;cnt
     ⍝ Create a temp file
     ⍝ folder - folder to create temp file in
     ⍝ ext - file extension
      :If 0=⎕NC'ext' ⋄ ext←'tmp' ⋄ :EndIf
      :If 0∊⍴ext ⋄ ext←'tmp' ⋄ :EndIf
      cnt←tn←0
      :Repeat
          name←folder,('/\'∊⍨¯1↑folder)↓'/',⎕A[?8⍴⍴⎕A],'.',ext
          :Trap 22 ⋄ tn←name ⎕NCREATE 0 ⋄ :EndTrap
          cnt+←1
      :Until (cnt>20)∨tn≠0
      'Unable to create temporary file'⎕SIGNAL tn↓22
    ∇

    ∇ {protect}Copy FmTo;CopyFileX;GetLastError ⍝ Copy file Fm -> To
      :Select APLVersion
      :Case '*nix'
          ∘
      :Case 'Win'
          :If 0=⎕NC'protect'     ⍝ Copy fails if <protect> and 'To' exists.
              protect←0          ⍝ Default unprotected copy.
          :EndIf
          'CopyFileX'⎕NA'I kernel32.C32∣CopyFile* <0T <0T I'
          :If 0=CopyFileX FmTo,protect
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'CopyFile error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Delete name;DeleteFileX;GetLastError;FindFirstFile;FindNextFile;FindClose;handle;rslt;ok;next;⎕IO;path
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          _SH'rm ',unixfix name
      :Case 'Win'
          'DeleteFileX'⎕NA'I kernel32.C32∣DeleteFile* <0T'
          ⎕NA'I4 kernel32.C32|GetLastError'
          :If ∨/'*?'∊name ⍝ wildcards?
              path←{(⌽∨\⌽⍵∊'\/')/⍵}name
              _FindDefine
              handle rslt←_FindFirstFile name
              :If 0=handle
                  :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
              :EndIf
              :If '.'≠⊃6⊃rslt
                  {}DeleteFileX⊂path,6⊃rslt
              :EndIf
              :While 1=0⊃ok next←_FindNextFile handle
                  :If '.'≠⊃6⊃next
                      {}DeleteFileX⊂path,6⊃next
                  :EndIf
              :EndWhile
              :If 0 18∨.≠ok next
                  ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
              :EndIf
              {}FindClose handle
          :Else
              :If 0=DeleteFileX⊂name
                  11 ⎕SIGNAL⍨'DeleteFile error:',⍕GetLastError
              :EndIf
          :EndIf
      :EndSelect
    ∇

    ∇ rslt←{amsk}Dir path;handle;next;ok;⎕IO;attrs;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError
     ⍝ 'FIX ME'⎕SIGNAL 11
     ⍝ Amsk is a 32 element bool attribute mask.
     ⍝ Only files with attributes corresponding to 1-s in the mask will be returned.
     ⍝ '*'s mark default attribute mask.
     ⍝
     ⍝        * [31] <=> READONLY
     ⍝          [30] <=> HIDDEN
     ⍝        * [29] <=> SYSTEM
     ⍝          [28] <=> undocumented
     ⍝        * [27] <=> DIRECTORY
     ⍝        * [26] <=> ARCHIVE
     ⍝          [25] <=> DEVICE
     ⍝        * [24] <=> NORMAL - only set if no other bits are set
     ⍝        * [23] <=> TEMPORARY
     ⍝        * [22] <=> SPARSE FILE
     ⍝        * [21] <=> REPARSE POINT
     ⍝        * [20] <=> COMPRESSED
     ⍝        * [19] <=> OFFLINE
     ⍝        * [18] <=> NOT CONTENT INDEXED
     ⍝        * [17] <=> ENCRYPTED
     ⍝        * rest <=> undocumented (but in the default set so that
     ⍝                   Microsoft can extend them)
    ⍝ rslt is a vector of character vectors of filenames

      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          :If 0=⎕NC'amsk'
              amsk←~(⍳32)∊30 28 25   ⍝ Default attribute mask.
          :EndIf
          _FindDefine
          handle rslt←_FindFirstFile path
          :If 0=handle
              ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11       ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          rslt←↓[0]↑rslt
          attrs←(32⍴2)⊤0⊃rslt                        ⍝ Get attributes into bits
          rslt←(amsk∧.≥attrs)⌿6⊃rslt                 ⍝ bin unwanted files and info
      :EndSelect
    ∇

    ∇ r←DirExists path
      r←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :Trap 11
              r←0<⍴_SH'ls -adl ',unixfix path
          :EndTrap
      :Case 'Win'
          r←0<⍬⍴⍴'.'List path
      :EndSelect
    ∇

    ∇ rslt←{amsk}DirX path;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError
     ⍝ 'FIX ME'⎕SIGNAL 11
     ⍝ Amsk is a 32 element bool attribute mask.
     ⍝ Only files with attributes corresponding to 1-s in the mask will be returned.
     ⍝ Amsk defaults to all attributes.
     ⍝ 0⊃rslt <=> 32 column boolean matrix of attribute bits
     ⍝          [;31] <=> READONLY
     ⍝          [;30] <=> HIDDEN
     ⍝          [;29] <=> SYSTEM
     ⍝          [;28] <=> undocumented
     ⍝          [;27] <=> DIRECTORY
     ⍝          [;26] <=> ARCHIVE
     ⍝          [;25] <=> undocumented
     ⍝          [;24] <=> NORMAL - only set if no other bits are set
     ⍝          [;23] <=> TEMPORARY
     ⍝          [;22] <=> SPARSE FILE
     ⍝          [;21] <=> REPARSE POINT
     ⍝          [;20] <=> COMPRESSED
     ⍝          [;19] <=> OFFLINE
     ⍝          [;18] <=> NOT CONTENT INDEXED
     ⍝          [;17] <=> ENCRYPTED
     ⍝          rest  <=> undocumented
     ⍝ 1⊃rslt <=> 7 column numeric matrix expressing the file creation time in ⎕TS format
     ⍝         if the file system does not support this then all columns are 0
     ⍝ 2⊃rslt <=> 7 column numeric matrix expressing the file last access time in ⎕TS format
     ⍝         if the file system does not support this then all columns are 0
     ⍝ 3⊃rslt <=> 7 column numeric matrix expressing the file last write time in ⎕TS format
     ⍝ 4⊃rslt <=> numeric vector giving the file size accurate up to 53 bits
     ⍝ 5⊃rslt <=> vector of character vectors giving the file names
     ⍝ 6⊃rslt <=> vector of character vectors giving the 8.3 file name for file systems
     ⍝         where it is appropriate and different from the file name
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          :If 0=⎕NC'amsk'
              amsk←32⍴1
          :EndIf
          _FindDefine
          handle rslt←_FindFirstFile path
          :If 0=handle
              ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          rslt←↓[0]↑rslt
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt                ⍝ Get attributes into bits
          rslt←(amsk∧.≥attrs)∘⌿¨rslt                ⍝ bin unwanted files and info
          rslt[1 2 3]←↑¨_Filetime_to_TS¨¨rslt[1 2 3]    ⍝ put times into ⎕ts format
          (4⊃rslt)←0(2*32)⊥⍉↑4⊃rslt                    ⍝ combine size elements
          rslt/⍨←5≠⍳8                               ⍝ bin the reserved elements
      :EndSelect
    ∇

    ∇ r←Exists name
     ⍝ Does file exist?
      r←1
      :Trap 22
          :Trap 19 ⍝ file access error means file exists
              ⎕NUNTIE(unixfix name)⎕NTIE 0
          :EndTrap
      :Else
          r←0
      :EndTrap
    ∇

    ∇ r←GetCurrentDirectory;GCD;GetLastError
     ⍝ Get Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          r←⊃_SH'pwd'
      :Case 'Win'
          'GCD'⎕NA'I kernel32.C32∣GetCurrentDirectory* I4 >0T'
          :If 0≠1⊃r←GCD 256 256
              r←2⊃r
          :Else
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'GetCurrentDirectory error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ r←GetText name;tn
     ⍝ Read a text file as single byte text
      tn←(unixfix name)⎕NTIE 0 ⋄ r←⎕NREAD tn(⎕DR' ')(⎕NSIZE tn) ⋄ ⎕NUNTIE tn
    ∇

    ∇ r←LikelyURL w
      →0↓⍨r←(⎕DR w)∊80 82
      r←{(0∊⍴⍵)<∧/⍵∊'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%-._~:/?#[]@!$&''()*+,;='}w ⍝ identify likely URIs
    ∇

    ∇ r←{file}List path;z;rslt;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError;isFile;filter
    ⍝ Return matrix containing
    ⍝ [;0] Name [;1] Length [;2] LastAccessTime [;3] IsDirectory
      ⎕IO←0
      :If isFile←2=⎕NC'file' ⋄ filter←(0<⍴,file)/'/',file ⋄ :Else ⋄ filter←'/*' ⋄ :EndIf
      filter←('/\'∊⍨¯1↑path)↓filter
      r←0 4⍴'' 0 0 0

      :Select APLVersion
      :Case '*nix'
          →(0∊⍴rslt←1 _SH'ls -al --time-style=full-iso ',unixfix path,isFile/filter)⍴0
          rslt←↑rslt
          rslt←' ',('total '≡6⍴rslt)↓[0]rslt
          →(0=1↑⍴r←((1↑⍴rslt),4)⍴0)⍴0
          z←∧⌿' '=rslt ⍝ entirely blank columns
          z←z∧10>+\z    ⍝ Do not split file names
          rslt←z⊂rslt
          r[;3]←'d'=(0⊃rslt)[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃⎕VFI,4⊃rslt ⍝ Size
          z←,(5⊃rslt),6⊃rslt ⋄ ((z∊'-:')/z)←' ' ⋄ z←((1↑⍴r),6)⍴1⊃⎕VFI z
          r[;2]←↓⌊z,1000×1|z[;5]                ⍝ Add msec to Timestamp
          r[;0]←{(⌽~∨\⌽⍵='/')/⍵}¨{(-+/∧\' '=⌽⍵)↓¨↓⍵}0 1↓8⊃rslt    ⍝ Name

      :Case 'Mac'
          →(0∊⍴rslt←1 _SH'stat -lt "%F %T %z" ',unixfix path,isFile/filter)⍴0
          r←((1↑⍴rslt),4)⍴0
          rslt←↑{⎕ML←3 ⋄ ⍵⊂⍨~{⍵∧9>+\⍵}' '=⍵}¨rslt
          r[;3]←'d'=0⊃¨rslt[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃¨⎕VFI¨rslt[;4]        ⍝ Size
          z←↑∊¨↓{w←⍵ ⋄ ((w∊'-:')/w)←' ' ⋄ 1⊃⎕VFI w}¨rslt[;5 6] ⍝
          r[;2]←↓z,0                            ⍝ 0 msec for MacOS to Timestamp
          r[;0]←rslt[;8]                        ⍝ Name

      :Case 'Win'
      ⍝ See DirX for explanations of results of _FindNextFile etc
          _FindDefine
          handle rslt←_FindFirstFile path,filter
          :If 0=handle
              :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          rslt←↓[0]↑rslt
          →(0=1↑⍴r←((1↑⍴0⊃rslt),4)⍴0)⍴0
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt                ⍝ Get attributes into bits
          r[;3]←(0⊃rslt)[;27]              ⍝ IsDirectory?
          r[;1]←0(2*32)⊥⍉↑4⊃rslt          ⍝ combine size elements
          r[;2]←_Filetime_to_TS¨3⊃rslt     ⍝ As ⎕TS vector
          r[;0]←6⊃rslt                     ⍝ Name
      :EndSelect
      r←r[⍋↑r[;0];]
    ∇

    ∇ MkDir path;CreateDirectory;GetLastError;err
      ⍝ Create a folder
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :If ~DirExists path
              1 _SH'mkdir ',unixfix path
              ('mkdir error on ',path)⎕SIGNAL 11/⍨~DirExists path
          :EndIf
      :Case 'Win'
          ⎕NA'I kernel32.C32∣CreateDirectory* <0T I4' ⍝ Try for best function
          →(0≠CreateDirectory path 0)⍴0 ⍝ 0 means "default security attributes"
          ⎕NA'I4 kernel32.C32|GetLastError'
          err ⎕SIGNAL⍨'CreateDirectory error:',⍕err←GetLastError
      :EndSelect
    ∇

    ∇ Move filenames;MoveFileX;MoveFileExA;GetLastError;err
    ⍝ Move (from to) - move/rename file
      :Select APLVersion
      :Case '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣MoveFileEx* <0T <0T I4' ⍝ Try for best function
          :If 0≠MoveFileExA filenames,3 ⍝ REPLACE_EXISTING (1) + COPY_ALLOWED (2)
              :Return
          :EndIf
          ⎕NA'I4 kernel32.C32|GetLastError'
          :Select err←GetLastError
          :Case 120                     ⍝ ERROR_CALL_NOT_IMPLIMENTED
              'MoveFileX'⎕NA'I Kernel32.C32∣MoveFile* <0T <0T' ⍝ accept 2nd best - win 95
              :If 0≠MoveFileX filenames
                  :Return
              :EndIf
              err←GetLastError
          :EndSelect
          11 ⎕SIGNAL⍨'MoveFile error:',⍕err
      :EndSelect
    ∇

    ∇ r←text PutText name;tn
     ⍝ Write text to file (must be single byte text)
      :Trap 0
          tn←name ⎕NCREATE 0
      :Else
          tn←name ⎕NTIE 0
          0 ⎕NRESIZE tn
      :EndTrap

      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ RmDir path;RemoveDirectoryA;GetLastError
     ⍝ Remove folder/directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣RemoveDirectory* <0T'
          →(0≠RemoveDirectory,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'RemoveDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ SetCurrentDirectory path;SCD;GetLastError
     ⍝ Set Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          'SCD'⎕NA'I kernel32.C32∣SetCurrentDirectory* <0T'
          →(0≠SCD,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'SetCurrentDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ r←{noext}SplitFilename filename;filesep;mask;path;file;ext
    ⍝ splits a filename into: path name ext
      noext←{6::0 ⋄ noext}''
      filesep←(~IsWin)↓'\/'
      mask←⌽∨\⌽filename∊filesep
      path←mask/filename
      file←(~mask)/filename
      :If noext
          r←path file
      :Else
          mask←∨\⌽<\⌽'.'=file
          ext←mask/file
          file←(~mask)/file
          r←path file ext
      :EndIf
    ∇

    :Section Windows
    ∇ rslt←_Filetime_to_TS filetime;⎕IO
      :If 1≠0⊃rslt←FileTimeToLocalFileTime filetime(⎕IO←0)
      :OrIf 1≠0⊃rslt←FileTimeToSystemTime(1⊃rslt)0
          rslt←0 0                   ⍝ if either call failed then zero the time elements
      :EndIf
      rslt←1 1 0 1 1 1 1 1/1⊃rslt    ⍝ remove day of week
    ∇

    ∇ _FindDefine;WIN32_FIND_DATA
      WIN32_FIND_DATA←'{I4 {I4 I4} {I4 I4} {I4 I4} {U4 U4} {I4 I4} T[260] T[14]}'
      'FindFirstFileX'⎕NA'I4 kernel32.C32|FindFirstFile* <0T >',WIN32_FIND_DATA
      'FindNextFileX'⎕NA'U4 kernel32.C32|FindNextFile* I4 >',WIN32_FIND_DATA
      ⎕NA'kernel32.C32|FindClose I4'
      ⎕NA'I4 kernel32.C32|FileTimeToLocalFileTime <{I4 I4} >{I4 I4}'
      ⎕NA'I4 kernel32.C32|FileTimeToSystemTime <{I4 I4} >{I2 I2 I2 I2 I2 I2 I2 I2}'
      ⎕NA'I4 kernel32.C32∣GetLastError'
    ∇

    ∇ rslt←_FindFirstFile name;⎕IO
      rslt←FindFirstFileX name(⎕IO←0)
      :If ¯1=0⊃rslt                   ⍝ INVALID_HANDLE_VALUE
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0        ⍝ shorten the file name at the null delimiter
          (1 7⊃rslt)_FindTrim←0        ⍝ and for the alternate name
      :EndIf
    ∇

    ∇ rslt←_FindNextFile handle;⎕IO
      rslt←FindNextFileX handle(⎕IO←0)
      :If 1≠0⊃rslt
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0             ⍝ shorten the filename
          (1 7⊃rslt)_FindTrim←0             ⍝ shorten the alternate name
      :EndIf
    ∇

    ∇ name←name _FindTrim ignored;⎕IO
     ⍝ Truncates a character vector at the null delimiting byte.
     ⍝ The null is not included in the result.
      ⎕IO←0
      name↑⍨←name⍳⎕UCS 0
    ∇

    ∇ {r}←{suppress}_CMD cmd
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕CMD cmd
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ r←IsWin
      r←'Win'≡APLVersion
    ∇

    :endsection

    :section *nix
    ∇ {r}←{suppress}_SH cmd
    ⍝ SH cover to suppress any error messages
    ⍝ suppress will suppress error from being signaled
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕SH cmd,' 2>/dev/null'
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ f←unixfix f
    ⍝ replaces Windows file separator \ with Unix file separator /
    ⍝ this approach is mindnumbingly simple and probably dangerous
    ⍝ which is why we call unixfix very cautiously
      :If (⊂APLVersion)∊'*nix' 'Mac' ⋄ ((f='\')/f)←'/' ⋄ :EndIf
    ∇

    ∇ r←APLVersion
      :Select 3↑⊃'.'⎕WG'APLVersion'
      :CaseList 'Lin' 'AIX' 'Sol'
          r←'*nix'
      :Case 'Win'
          r←'Win'
      :Case 'Mac'
          r←'Mac'
      :Else
          ... ⍝ unknown version
      :EndSelect
    ∇
    :endsection

    ∇ r←a FREAD w;t
      t←w ⎕FSTIE 0
      r←⎕FREAD t a
      ⎕FUNTIE t
    ∇
:EndNamespace

:Namespace Files

⍝ Provides cover functions for many file operations
⍝ Eventually all will be implemented for both Windows and *nix

    (⎕IO ⎕ML)←1

    ∇ r←text AppendText name;tn
     ⍝ Append text to existing file (must be single byte text)
      tn←name ⎕NTIE 0
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ (tn name)←{ext}CreateTemp folder;cnt
     ⍝ Create a temp file
     ⍝ folder - folder to create temp file in
     ⍝ ext - file extension
      :If 0=⎕NC'ext' ⋄ ext←'tmp' ⋄ :EndIf
      :If 0∊⍴ext ⋄ ext←'tmp' ⋄ :EndIf
      cnt←tn←0
      :Repeat
          name←folder,('/\'∊⍨¯1↑folder)↓'/',⎕A[?8⍴⍴⎕A],'.',ext
          :Trap 22 ⋄ tn←name ⎕NCREATE 0 ⋄ :EndTrap
          cnt+←1
      :Until (cnt>20)∨tn≠0
      'Unable to create temporary file'⎕SIGNAL tn↓22
    ∇

    ∇ {protect}Copy FmTo;CopyFileX;GetLastError ⍝ Copy file Fm -> To
      :Select APLVersion
      :Case '*nix'
          ∘
      :Case 'Win'
          :If 0=⎕NC'protect'     ⍝ Copy fails if <protect> and 'To' exists.
              protect←0          ⍝ Default unprotected copy.
          :EndIf
          'CopyFileX'⎕NA'I kernel32.C32∣CopyFile* <0T <0T I'
          :If 0=CopyFileX FmTo,protect
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'CopyFile error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Delete name;DeleteFileX;GetLastError;FindFirstFile;FindNextFile;FindClose;handle;rslt;ok;next;⎕IO;path
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          _SH'rm ',unixfix name
      :Case 'Win'
          'DeleteFileX'⎕NA'I kernel32.C32∣DeleteFile* <0T'
          ⎕NA'I4 kernel32.C32|GetLastError'
          :If ∨/'*?'∊name ⍝ wildcards?
              path←{(⌽∨\⌽⍵∊'\/')/⍵}name
              _FindDefine
              handle rslt←_FindFirstFile name
              :If 0=handle
                  :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
              :EndIf
              :If '.'≠⊃6⊃rslt
                  {}DeleteFileX⊂path,6⊃rslt
              :EndIf
              :While 1=0⊃ok next←_FindNextFile handle
                  :If '.'≠⊃6⊃next
                      {}DeleteFileX⊂path,6⊃next
                  :EndIf
              :EndWhile
              :If 0 18∨.≠ok next
                  ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
              :EndIf
              {}FindClose handle
          :Else
              :If 0=DeleteFileX⊂name
                  11 ⎕SIGNAL⍨'DeleteFile error:',⍕GetLastError
              :EndIf
          :EndIf
      :EndSelect
    ∇

    ∇ rslt←{amsk}Dir path;handle;next;ok;⎕IO;attrs;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError
     ⍝ 'FIX ME'⎕SIGNAL 11
     ⍝ Amsk is a 32 element bool attribute mask.
     ⍝ Only files with attributes corresponding to 1-s in the mask will be returned.
     ⍝ '*'s mark default attribute mask.
     ⍝
     ⍝        * [31] <=> READONLY
     ⍝          [30] <=> HIDDEN
     ⍝        * [29] <=> SYSTEM
     ⍝          [28] <=> undocumented
     ⍝        * [27] <=> DIRECTORY
     ⍝        * [26] <=> ARCHIVE
     ⍝          [25] <=> DEVICE
     ⍝        * [24] <=> NORMAL - only set if no other bits are set
     ⍝        * [23] <=> TEMPORARY
     ⍝        * [22] <=> SPARSE FILE
     ⍝        * [21] <=> REPARSE POINT
     ⍝        * [20] <=> COMPRESSED
     ⍝        * [19] <=> OFFLINE
     ⍝        * [18] <=> NOT CONTENT INDEXED
     ⍝        * [17] <=> ENCRYPTED
     ⍝        * rest <=> undocumented (but in the default set so that
     ⍝                   Microsoft can extend them)
    ⍝ rslt is a vector of character vectors of filenames

      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          :If 0=⎕NC'amsk'
              amsk←~(⍳32)∊30 28 25   ⍝ Default attribute mask.
          :EndIf
          _FindDefine
          handle rslt←_FindFirstFile path
          :If 0=handle
              ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11       ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          rslt←↓[0]↑rslt
          attrs←(32⍴2)⊤0⊃rslt                        ⍝ Get attributes into bits
          rslt←(amsk∧.≥attrs)⌿6⊃rslt                 ⍝ bin unwanted files and info
      :EndSelect
    ∇

    ∇ r←DirExists path
      r←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :Trap 11
              r←0<⍴_SH'ls -adl ',unixfix path
          :EndTrap
      :Case 'Win'
          r←0<⍬⍴⍴'.'List path
      :EndSelect
    ∇

    ∇ rslt←{amsk}DirX path;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError
     ⍝ 'FIX ME'⎕SIGNAL 11
     ⍝ Amsk is a 32 element bool attribute mask.
     ⍝ Only files with attributes corresponding to 1-s in the mask will be returned.
     ⍝ Amsk defaults to all attributes.
     ⍝ 0⊃rslt <=> 32 column boolean matrix of attribute bits
     ⍝          [;31] <=> READONLY
     ⍝          [;30] <=> HIDDEN
     ⍝          [;29] <=> SYSTEM
     ⍝          [;28] <=> undocumented
     ⍝          [;27] <=> DIRECTORY
     ⍝          [;26] <=> ARCHIVE
     ⍝          [;25] <=> undocumented
     ⍝          [;24] <=> NORMAL - only set if no other bits are set
     ⍝          [;23] <=> TEMPORARY
     ⍝          [;22] <=> SPARSE FILE
     ⍝          [;21] <=> REPARSE POINT
     ⍝          [;20] <=> COMPRESSED
     ⍝          [;19] <=> OFFLINE
     ⍝          [;18] <=> NOT CONTENT INDEXED
     ⍝          [;17] <=> ENCRYPTED
     ⍝          rest  <=> undocumented
     ⍝ 1⊃rslt <=> 7 column numeric matrix expressing the file creation time in ⎕TS format
     ⍝         if the file system does not support this then all columns are 0
     ⍝ 2⊃rslt <=> 7 column numeric matrix expressing the file last access time in ⎕TS format
     ⍝         if the file system does not support this then all columns are 0
     ⍝ 3⊃rslt <=> 7 column numeric matrix expressing the file last write time in ⎕TS format
     ⍝ 4⊃rslt <=> numeric vector giving the file size accurate up to 53 bits
     ⍝ 5⊃rslt <=> vector of character vectors giving the file names
     ⍝ 6⊃rslt <=> vector of character vectors giving the 8.3 file name for file systems
     ⍝         where it is appropriate and different from the file name
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          :If 0=⎕NC'amsk'
              amsk←32⍴1
          :EndIf
          _FindDefine
          handle rslt←_FindFirstFile path
          :If 0=handle
              ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          rslt←↓[0]↑rslt
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt                ⍝ Get attributes into bits
          rslt←(amsk∧.≥attrs)∘⌿¨rslt                ⍝ bin unwanted files and info
          rslt[1 2 3]←↑¨_Filetime_to_TS¨¨rslt[1 2 3]    ⍝ put times into ⎕ts format
          (4⊃rslt)←0(2*32)⊥⍉↑4⊃rslt                    ⍝ combine size elements
          rslt/⍨←5≠⍳8                               ⍝ bin the reserved elements
      :EndSelect
    ∇

    ∇ r←Exists name
     ⍝ Does file exist?
      r←1
      :Trap 22
          :Trap 19 ⍝ file access error means file exists
              ⎕NUNTIE(unixfix name)⎕NTIE 0
          :EndTrap
      :Else
          r←0
      :EndTrap
    ∇

    ∇ r←GetCurrentDirectory;GCD;GetLastError
     ⍝ Get Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          r←⊃_SH'pwd'
      :Case 'Win'
          'GCD'⎕NA'I kernel32.C32∣GetCurrentDirectory* I4 >0T'
          :If 0≠1⊃r←GCD 256 256
              r←2⊃r
          :Else
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'GetCurrentDirectory error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ r←GetText name;tn
     ⍝ Read a text file as single byte text
      tn←(unixfix name)⎕NTIE 0 ⋄ r←⎕NREAD tn(⎕DR' ')(⎕NSIZE tn) ⋄ ⎕NUNTIE tn
    ∇

    ∇ r←LikelyURL w
      →0↓⍨r←(⎕DR w)∊80 82
      r←{(0∊⍴⍵)<∧/⍵∊'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%-._~:/?#[]@!$&''()*+,;='}w ⍝ identify likely URIs
    ∇

    ∇ r←{file}List path;z;rslt;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError;isFile;filter
    ⍝ Return matrix containing
    ⍝ [;0] Name [;1] Length [;2] LastAccessTime [;3] IsDirectory
      ⎕IO←0
      :If isFile←2=⎕NC'file' ⋄ filter←(0<⍴,file)/'/',file ⋄ :Else ⋄ filter←'/*' ⋄ :EndIf
      filter←('/\'∊⍨¯1↑path)↓filter
      r←0 4⍴'' 0 0 0

      :Select APLVersion
      :Case '*nix'
          →(0∊⍴rslt←1 _SH'ls -al --time-style=full-iso ',unixfix path,isFile/filter)⍴0
          rslt←↑rslt
          rslt←' ',('total '≡6⍴rslt)↓[0]rslt
          →(0=1↑⍴r←((1↑⍴rslt),4)⍴0)⍴0
          z←∧⌿' '=rslt ⍝ entirely blank columns
          z←z∧10>+\z    ⍝ Do not split file names
          rslt←z⊂rslt
          r[;3]←'d'=(0⊃rslt)[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃⎕VFI,4⊃rslt ⍝ Size
          z←,(5⊃rslt),6⊃rslt ⋄ ((z∊'-:')/z)←' ' ⋄ z←((1↑⍴r),6)⍴1⊃⎕VFI z
          r[;2]←↓⌊z,1000×1|z[;5]                ⍝ Add msec to Timestamp
          r[;0]←{(⌽~∨\⌽⍵='/')/⍵}¨{(-+/∧\' '=⌽⍵)↓¨↓⍵}0 1↓8⊃rslt    ⍝ Name

      :Case 'Mac'
          →(0∊⍴rslt←1 _SH'stat -lt "%F %T %z" ',unixfix path,isFile/filter)⍴0
          r←((1↑⍴rslt),4)⍴0
          rslt←↑{⎕ML←3 ⋄ ⍵⊂⍨~{⍵∧9>+\⍵}' '=⍵}¨rslt
          r[;3]←'d'=0⊃¨rslt[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃⎕VFI¨rslt[;4]        ⍝ Size
          z←↑∊¨↓{w←⍵ ⋄ ((w∊'-:')/w)←' ' ⋄ 1⊃⎕VFI w}¨rslt[;5 6] ⍝
          r[;2]←↓z,0                            ⍝ 0 msec for MacOS to Timestamp
          r[;0]←rslt[;8]                        ⍝ Name

      :Case 'Win'
      ⍝ See DirX for explanations of results of _FindNextFile etc
          _FindDefine
          handle rslt←_FindFirstFile path,filter
          :If 0=handle
              :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          rslt←↓[0]↑rslt
          →(0=1↑⍴r←((1↑⍴0⊃rslt),4)⍴0)⍴0
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt                ⍝ Get attributes into bits
          r[;3]←(0⊃rslt)[;27]              ⍝ IsDirectory?
          r[;1]←0(2*32)⊥⍉↑4⊃rslt          ⍝ combine size elements
          r[;2]←_Filetime_to_TS¨3⊃rslt     ⍝ As ⎕TS vector
          r[;0]←6⊃rslt                     ⍝ Name
      :EndSelect
      r←r[⍋↑r[;0];]
    ∇

    ∇ MkDir path;CreateDirectory;GetLastError;err
      ⍝ Create a folder
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :If ~DirExists path
              1 _SH'mkdir ',unixfix path
              ('mkdir error on ',path)⎕SIGNAL 11/⍨~DirExists path
          :EndIf
      :Case 'Win'
          ⎕NA'I kernel32.C32∣CreateDirectory* <0T I4' ⍝ Try for best function
          →(0≠CreateDirectory path 0)⍴0 ⍝ 0 means "default security attributes"
          ⎕NA'I4 kernel32.C32|GetLastError'
          err ⎕SIGNAL⍨'CreateDirectory error:',⍕err←GetLastError
      :EndSelect
    ∇

    ∇ Move filenames;MoveFileX;MoveFileExA;GetLastError;err
    ⍝ Move (from to) - move/rename file
      :Select APLVersion
      :Case '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣MoveFileEx* <0T <0T I4' ⍝ Try for best function
          :If 0≠MoveFileExA filenames,3 ⍝ REPLACE_EXISTING (1) + COPY_ALLOWED (2)
              :Return
          :EndIf
          ⎕NA'I4 kernel32.C32|GetLastError'
          :Select err←GetLastError
          :Case 120                     ⍝ ERROR_CALL_NOT_IMPLIMENTED
              'MoveFileX'⎕NA'I Kernel32.C32∣MoveFile* <0T <0T' ⍝ accept 2nd best - win 95
              :If 0≠MoveFileX filenames
                  :Return
              :EndIf
              err←GetLastError
          :EndSelect
          11 ⎕SIGNAL⍨'MoveFile error:',⍕err
      :EndSelect
    ∇

    ∇ r←text PutText name;tn
     ⍝ Write text to file (must be single byte text)
      :Trap 0
          tn←name ⎕NCREATE 0
      :Else
          tn←name ⎕NTIE 0
          0 ⎕NRESIZE tn
      :EndTrap

      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ RmDir path;RemoveDirectoryA;GetLastError
     ⍝ Remove folder/directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣RemoveDirectory* <0T'
          →(0≠RemoveDirectory,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'RemoveDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ SetCurrentDirectory path;SCD;GetLastError
     ⍝ Set Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          'SCD'⎕NA'I kernel32.C32∣SetCurrentDirectory* <0T'
          →(0≠SCD,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'SetCurrentDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ r←{noext}SplitFilename filename;filesep;mask;path;file;ext
    ⍝ splits a filename into: path name ext
      noext←{6::0 ⋄ noext}''
      filesep←(~IsWin)↓'\/'
      mask←⌽∨\⌽filename∊filesep
      path←mask/filename
      file←(~mask)/filename
      :If noext
          r←path file
      :Else
          mask←∨\⌽<\⌽'.'=file
          ext←mask/file
          file←(~mask)/file
          r←path file ext
      :EndIf
    ∇

    :Section Windows
    ∇ rslt←_Filetime_to_TS filetime;⎕IO
      :If 1≠0⊃rslt←FileTimeToLocalFileTime filetime(⎕IO←0)
      :OrIf 1≠0⊃rslt←FileTimeToSystemTime(1⊃rslt)0
          rslt←0 0                   ⍝ if either call failed then zero the time elements
      :EndIf
      rslt←1 1 0 1 1 1 1 1/1⊃rslt    ⍝ remove day of week
    ∇

    ∇ _FindDefine;WIN32_FIND_DATA
      WIN32_FIND_DATA←'{I4 {I4 I4} {I4 I4} {I4 I4} {U4 U4} {I4 I4} T[260] T[14]}'
      'FindFirstFileX'⎕NA'I4 kernel32.C32|FindFirstFile* <0T >',WIN32_FIND_DATA
      'FindNextFileX'⎕NA'U4 kernel32.C32|FindNextFile* I4 >',WIN32_FIND_DATA
      ⎕NA'kernel32.C32|FindClose I4'
      ⎕NA'I4 kernel32.C32|FileTimeToLocalFileTime <{I4 I4} >{I4 I4}'
      ⎕NA'I4 kernel32.C32|FileTimeToSystemTime <{I4 I4} >{I2 I2 I2 I2 I2 I2 I2 I2}'
      ⎕NA'I4 kernel32.C32∣GetLastError'
    ∇

    ∇ rslt←_FindFirstFile name;⎕IO
      rslt←FindFirstFileX name(⎕IO←0)
      :If ¯1=0⊃rslt                   ⍝ INVALID_HANDLE_VALUE
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0        ⍝ shorten the file name at the null delimiter
          (1 7⊃rslt)_FindTrim←0        ⍝ and for the alternate name
      :EndIf
    ∇

    ∇ rslt←_FindNextFile handle;⎕IO
      rslt←FindNextFileX handle(⎕IO←0)
      :If 1≠0⊃rslt
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0             ⍝ shorten the filename
          (1 7⊃rslt)_FindTrim←0             ⍝ shorten the alternate name
      :EndIf
    ∇

    ∇ name←name _FindTrim ignored;⎕IO
     ⍝ Truncates a character vector at the null delimiting byte.
     ⍝ The null is not included in the result.
      ⎕IO←0
      name↑⍨←name⍳⎕UCS 0
    ∇

    ∇ {r}←{suppress}_CMD cmd
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕CMD cmd
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ r←IsWin
      r←'Win'≡APLVersion
    ∇

    :endsection

    :section *nix
    ∇ {r}←{suppress}_SH cmd
    ⍝ SH cover to suppress any error messages
    ⍝ suppress will suppress error from being signaled
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕SH cmd,' 2>/dev/null'
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ f←unixfix f
    ⍝ replaces Windows file separator \ with Unix file separator /
    ⍝ this approach is mindnumbingly simple and probably dangerous
    ⍝ which is why we call unixfix very cautiously
      :If (⊂APLVersion)∊'*nix' 'Mac' ⋄ ((f='\')/f)←'/' ⋄ :EndIf
    ∇

    ∇ r←APLVersion
      :Select 3↑⊃'.'⎕WG'APLVersion'
      :CaseList 'Lin' 'AIX' 'Sol'
          r←'*nix'
      :Case 'Win'
          r←'Win'
      :Case 'Mac'
          r←'Mac'
      :Else
          ... ⍝ unknown version
      :EndSelect
    ∇
    :endsection

    ∇ r←a FREAD w;t
      t←w ⎕FSTIE 0
      r←⎕FREAD t a
      ⎕FUNTIE t
    ∇
:EndNamespace


prelude: replaced usages of {A}⍠{B} with {A;B}


Bug fix, better text, code improvements.

:Class index : MiPageSample

    :SECTION GLOBALS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    :Field TYPES←'Simple' 'Advanced'⍝ Types of samples (if not App)
    :Field GROUPS      ⍝ Names of groups of elements
    :Field REFS        ⍝ ... their refs
    :Field APPS        ⍝ List of all apps
    :Field SAMPLES     ⍝ List of all samples (well, groups and controls really)
    :Field INFOLONG    ⍝ Long desc of each control
    :Field INFOSHORT   ⍝ Short (1-3 words) desc of each control
    :Field NAMESLONG   ⍝ Elements that have long info
    :Field NAMESSHORT  ⍝ Elements that have short info
    :Field SEARCH←''   ⍝ Last-search cache
    :Field CONTROLS←'' ⍝ All control names
    :Field APPDESCS    ⍝ Sample Apps' Description::
    :Field APPCTRLS    ⍝ Sample Apps' Control:: (i.e. the title)
    :Field CORE        ⍝ Core subset of controls
    :Field STR←''      ⍝ String to search for

    :ENDSECTION

    :SECTION UTILITIES ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    Q←'"'∘,,∘'"'  ⍝ Surround with quotes, a.k.a. APL dragon

    D←{(×≢⍵)/' – ',⍵} ⍝ Prepend a dash if non-empty

    P←{1⌽(×≢#.HTMLInput.dtlb ⍵)/') (',⍵} ⍝ Surround with parens if non-empty

    Words←{(1↓¨(' '∘=⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces

    SpaceToDir←{'Examples/',1↓⍵} ⍝ Convert Space name to Folder name

    Frame←{'.iframed' ('src=',Q⍵,'?NoWrapper=1') New _.iframe}

    NewWinA←{('target' '_blank')('href=',Q⊃⌽⍵) New _.a (⊃⍵)} ⍝ New link that opens in a new window/tab

    NewDiv←{⍵ New _.div} ⍝ Make a div

    Horz←{⍺←⊢ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1} ⍝ Horizontal StackPanel

    Type←{P(1⍳⍨∨/¨TYPES⍷¨⊂⍺)⊃(TYPES,¨⊂' sample for ',⍵),⊂'Sample app'} ⍝ Simple/Advanced/App?

    Dread←{0::,⊂'[file read failed]' ⋄ #.UnicodeFile.ReadNestedText #.Boot.AppRoot,⍵,'.dyalog'} ⍝ Read dyalog file

    Dlist←{0::⍬ ⋄ ¯7↓¨6⊃#.Files.DirX #.Boot.AppRoot,⍵,'/*.dyalog'} ⍝ List dyalog files

    NodeID←{⍺←⊢ ⋄ ('#node',⊃⍵)∘,¨⍕¨⍺+⍳⍴⊃⌽⍵} ⍝ Generate node ids (⍵='L' items) optional (⍺=offset)

    Split←{{⍵↑⍨+/∨\' '≠⌽⍵}¨('_'⎕R' ')⍵((⍳↑⊣){⍺⍵}(⍳↓⊣))'_'} ⍝ Split at first _ and fix _s

    In←{∨/¨⊃⍷¨/{⎕SE.Dyalog.Utils.lcase¨eis ⍵}¨⍺ ⍵} ⍝ Case-insensitive find

    Names←{1↓¨⍵↑¨⍨¯1+⍵⍳¨':'} ⍝ Extract section names from set of several 'name:: description'

    AddShortInfo←{⍵,¨P¨(4+⍴¨⍵)↓¨(INFOSHORT,⊂'')[NAMESSHORT⍳⍵]} ⍝ ⍵ (ShortDesc)

    AddLongInfo←{(New¨_.strong,¨⍵),¨D¨(4+⍴¨⍵)↓¨(INFOLONG,⊂'')[NAMESLONG⍳⍵]} ⍝ ⍵ - LongDesc

    ScoreIn←{(100⊥⌽∨/¨TYPES⍷¨⊂⍵)×(⊂⍺)(∊×⍳⍨)Words'Control'Section Dread ⍵} ⍝ ⍺'s relevance in ⍵ (0=none 1=high 2=lower...)

      Section←{ ⍝ extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)'
          opts←('Mode' 'M')('DotAll' 1)
          res←(regex ⎕S'\1\2'⍠opts)⍵
          (1↓⊃res)~'⍝'
      }

      JSsafe←{ ⍝ Make text JS safe by escaping problematic chars
          fr←'''' '"' '\\' '\n' '\r',⎕UCS 9 8 12               ⍝ ' " \ NL CR TB BS FF
          to←'\\''' '\\"' '\\\\' '\\n' '\\r' '\\t' '\\b' '\\f' ⍝ problematic in regex too :-)
          Q(fr ⎕R to⍠'Mode' 'D')⍵ ⍝ Replace problematic chars and sourround with quotes
      }

    ∇ js←APLtoJS vtv;act;data
      :Access public shared
     ⍝ Convert vectors of pseudo JS (generated by Replace, Execute, et al.) to proper JS

      js←''
      :For act data :In eis vtv
          data←JSsafe⊃⌽eis data ⍝ the last or only one
          :Select ⊃eis act      ⍝ the first or only one
          :Case 'assign'
              js,←(⊃⌽act),' = ',data,';' ⍝ variable = "data"
          :Case 'execute'
              js,←'eval(',data,');' ⍝ eval("data")
          :Case 'replace'
              js,←'$("',(⊃⌽act),'").html(',data,');' ⍝ $("#myid").html("data")
          :Else
              js,←'$("',(⊃⌽act),'").',(⊃act),'(',data,');' ⍝ $("#myid").append("data")
          :EndSelect
      :EndFor
    ∇

    :ENDSECTION

    :SECTION MAIN ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ Compose;left;mid;right;sp;style
      :Access public

     ⍝ Initialize globals
      SAMPLES←0 3⍴⍬
      GROUPS←'Dyalog Controls' 'Base HTML' 'SyncFusion' 'JQueryUI'
      REFS←_DC _html _SF _JQ
      INFOSHORT←'⍝',¨Dread'Examples/Data/infoShort'
      INFOLONG←'⍝',¨Dread'Examples/Data/infoLong'
      NAMESSHORT←Names INFOSHORT
      NAMESLONG←Names INFOLONG

      '#title'Add _.title'MS3: About' ⍝ After the : will be updated

      Add _.StyleSheet'/Styles/homepage.css'
      style←''
      style,←'#leftBar {background-color:inherit;margin-right:6px;} '
      style,←'.menu {padding-left,padding-right:0;} '
      style,←'.cat {font-size:12pt;cursor:pointer;border:1px solid transparent;padding:0 4px 2px 4px;} '
      style,←'.cat:hover {background:linear-gradient(to bottom,#ffbb60 0%,#f37603 100%);border:1px solid #f9cb59;font-weight:bold;} '
      style,←'.menuitem {margin-bottom,margin-left:0px;padding-left:2px;cursor:pointer;} '
      style,←'.menuitem:hover {background-color:highlight;} '
      style,←'.framed {width:730px;max-height:600px;min-height:400px;border-left:1px inset;overflow-y:auto;} '
      style,←'.iframed {width:730px;max-height:600px;min-height:400px;border:2px inset;overflow-y:auto;background-color:white;} '
      style,←'.listitem {margin:0px;padding:4px 4px 4px 26px;text-indent:-22px;cursor:pointer;box-sizing:border-box;} '
      style,←'.listitem:before {content:"▶";padding: 1px 3px 3px 4px;'
      style,←'    background: ThreeDFace;'
      style,←'    color: ButtonText;'
      style,←'    border-radius: 4px;'
      style,←'    border: solid 1px ThreeDLightShadow;'
      style,←'    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);'
      style,←'    transition-duration: 0.2s;'
      style,←'    margin-right: 4px;'
      style,←'}'
      style,←'.listitem:active:before, #SampleTitle a:active:after {'
      style,←'    box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);'
      style,←'    background: ThreeDShadow;'
      style,←'    border: solid 1px ThreeDDarkShadow;'
      style,←'}'
      style,←'.listitem:hover:before, #SampleTitle a:hover:after {'
      style,←'    background: ThreeDHighlight;'
      style,←'    border: solid 1px ThreeDFace;'
      style,←'    text-decoration: none;'
      style,←'}'
      style,←'.noitems {margin:0px;padding:4px;cursor:not-allowed;} '
      style,←'.samplesource {overflow-x:auto;width:730px;background-color:#e5e5cc;border:2px inset;} '
      style,←'#SampleTitle a:hover {text-decoration: none}'
      style,←'#SampleTitle a:after {content:"➚";padding: 0px 4px;'
      style,←'    background: ThreeDFace;'
      style,←'    color: ButtonText;'
      style,←'    border-radius: 4px;'
      style,←'    border: solid 1px ThreeDLightShadow;'
      style,←'    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);'
      style,←'    transition-duration: 0.2s;'
      style,←'    margin-left: 8px;'
      style,←'    font-weight: normal;'
      style,←'}'
      Add _.style style

      (left mid)←NewDiv¨'#leftBar' '#midBar' ⍝ Create panes
      sp←Add'mainSP'Horz left mid

      sp.Items[1].style←⊂'width: 200px; max-height: 450px;'
      sp.Items[2].style←⊂'margin: 5px;'
      sp.style←'height: 450px;background-color:inherit;'

      PopulateLeft left
      PopulateMid mid
    ∇

    ∇ PopulateLeft thediv;class;depths;group;items;ref;samples;vp;search;style;menu;i;fs;ac;text;stuff;treeall;treecore;names;tree

      stuff←''
      tree←0 3⍴0 '' ''

         ⍝ SEARCH FIELD ⍝⍝⍝
      stuff,←New _.EditField'searchfield'⍝).On'change' 'OnSearch'('str' 'val')
      (stuff,←'#searchbutton' '.menu'New _.button'Search').On'click' 'OnSearch'('str' '#searchfield' 'val')

      thediv.Add Horz stuff
      thediv.Add _.hr

      ⍝ SAMPLE APPS ⍝⍝⍝
      APPS←(Dlist'Examples/Apps')~⊂'index'
      APPDESCS←(⊂'Description')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      APPCTRLS←(⊂'Control')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      ('.cat'thediv.Add _.span'Sample Apps').On'click' 'OnAppHeader'
      APPS{tree⍪←1 ⍵('nodeA',⍺)}¨APPCTRLS
      tree←'#treeA'thediv.Add _.ejTreeView tree
      tree.On'nodeSelect' 'OnApp'('node' 'eval' 'argument.id')

      thediv.Add _.hr

      ('#node01All' '.cat'thediv.Add _.span'Controls').On'click' 'OnTree'
      CORE←⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/core -noname'
      treecore←1 3⍴1 'Core' 'node0_Core'
      treeall←1 3⍴1 'All' 'node00All'

      :For group class :InEach GROUPS(3↓¨⍕¨REFS) ⍝ Remove leading #._
          names←⎕THIS.⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/tree',class,' -noname'
          CONTROLS,←⊂names[;2]/⍨(⊢=⌈/)names[;1]
          ⍎class,'←names'

          :If ∨/names[;2]∊CORE
              treecore⍪←2(group,P'_',class)('node__',class)
              treecore⍪←3,0 1↓names⌿⍨names[;2]∊CORE
          :EndIf

          treeall⍪←2(group,P'_',class)('node_',class)
          treeall⍪←⍎class

      :EndFor

      tree←'#treeC' 'style="max-height:260px"'thediv.Add _.ejTreeView,⊂treecore⍪treeall
      tree.On'nodeSelect' 'OnTree'('node' 'eval' 'argument.id')
    ∇

    ∇ PopulateMid mid;url;code;frame;mya

     ⍝ Read framed pages
      url←'Examples/Apps/About'
      code←Dread url
     ⍝ Create and fill placeholder for title header
      mya←('#SampleTitle'mid.Add _.h2).Add NewWinA('Control'Section code)url
     ⍝ Create and fill placeholder for description line
      '#SampleDesc'mid.Add _.p('Description'Section code)
     ⍝ Create and fill placeholder for embedded page
      frame←mid.Add NewDiv'#SampleFrame'
      frame.Add Frame url
      '#SampleSource' '.samplesource'mid.Add _.div⍝,⊂'x-small;border:none'#.HTMLInput.APLToHTMLColour code ⍝ No source on page load
    ∇

    :ENDSECTION

    :SECTION CALLBACKS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ r←OnTree;node;descs;out;items;spacectrl;control;spacedir;files;file;i;url;code;ctrlsec;desc;item;title;currctrls;core;names;class;group;cat;score
      :Access Public
      node←GetNode
      :If '0'∊node ⍝ Core or All
          core←'_'=2⊃node
          currctrls←(CORE∘∩⍣core)⊃,/CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽core↓node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←(2↓node)(2↓node,' Controls')((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃node ⍝ Class or core subset thereof
          core←'_'=2⊃node
          node↓⍨←core ⍝ Remove additional _ from '__html' if core-only
          i←REFS⍳⍎node
          currctrls←(CORE∘∩⍣core)i⊃CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽core↓node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←node((i⊃GROUPS),P node)((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃⌽node ⍝ Category
          (class cat)←Split node
          names←⍎class
          i←(names[;3]/⍨(⊢=⌊/)names[;1])⍳⊂'node',node
          currctrls←1↓i⊃names[;2]⊂⍨(⊢=⌊/)names[;1]
          group←GROUPS⊃⍨REFS⍳⍎'_',class
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',class)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←('_',class)(group,(P'_',class),' ',cat)(' controls',⍨⍕⍴currctrls)out''
      :Else ⍝ Control
          spacectrl←'_',('_'⎕R'.')node
          (class control)←Split node
          spacedir←'Examples/',class
          files←Dlist spacedir
          out←NewDiv'.framed'
          CURRFILES←0 2⍴0 'url' ⍝ Score URL
          :For file :In files
              url←spacedir,'/',file
              CURRFILES⍪←(spacectrl ScoreIn url)url
          :EndFor
          CURRFILES⌿⍨←×CURRFILES[;1] ⍝ Weed out 0-score samples
          :If 0∊⍴CURRFILES ⍝ Failed to find a sample, so perform a search instead
              STR←spacectrl ⍝ Pass implicit argument to OnSearch
              r←OnSearch ⍝ Capture and modify result
              (1⊃r)←spacectrl
              (2⊃r)←'_',class,'.',AddShortInfo⊂control
              (3⊃r)←(4↓⊃AddLongInfo,⊂control),' ',('0'=⊃2⊃r)/3⊃r
          :Else
              CURRFILES←CURRFILES[⍋CURRFILES[;1];2] ⍝ Sort by score
              r←spacectrl Update⊃CURRFILES
          :EndIf
      :EndIf
      r←GenJS r
    ∇

    ∇ r←OnAppHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      descs←APPCTRLS,¨' – '∘,¨APPDESCS
      out←NewDiv'.framed'
      items←APPS{('nodeB',⍺)out.Add _.p ⍵}¨descs
      items.Set⊂'.listitem'
      items.On⊂'click' 'OnApp'
      r←GenJS'Apps' 'Small applications to showcase MiServer 3.0 functionality'(' apps',⍨≢APPS)out''
    ∇

    ∇ r←OnCtrlHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      r←GenJS'Controls' 'Something about controls' 'Controls' 'list of all controls with long descs' ''
    ∇

    ∇ r←OnApp
      :Access Public
     ⍝ Gets called upon selection in Sample Apps tree
      r←GenJS Update'Examples/Apps/',1↓GetNode
    ∇

    ∇ r←OnSample
      :Access Public
      :Select _event
      :Case 'click'
          r←GenJS Update CURRFILES⊃⍨⍎5↓_what ⍝ id="nodeS23"
      :Case 'contextmenu'
          r←Execute'window.open("','");',⍨CURRFILES⊃⍨⍎5↓_what ⍝ id="nodeS23"
      :EndSelect
    ∇

    ∇ r←OnSearch;str;terms;out;i;dir;files;file;url;code;ctrlsec;desc;item;title
      :Access Public

      str←(1+STR≡'')⊃STR(Get'str') ⍝ get search string

      :If ×≢str ⍝ Continue only if there is a search string
      ⍝⍝⍝ Controls
          terms←INFOSHORT/⍨str In INFOSHORT
          terms,←terms~⍨INFOLONG/⍨str In INFOLONG
          terms←'.',¨∪Names terms
      ⍝⍝⍝ Samples
          out←NewDiv'.framed'
          CURRFILES←''
          i←0
          :For dir :In 'Apps' 'html' 'HTMLplus' 'DC' 'JQ' 'SF'
              files←Dlist'Examples/',dir
              :For file :In files~⊂'index'
                  url←'Examples/',dir,'/',file
                  code←Dread url
                  ctrlsec←'Control'Section code
                  desc←⊂'Description'Section code
                  :If ∨/str In desc
                  :OrIf ∨/∊(terms,⊂str)In¨⊂ctrlsec ⍝ always case sensitive
                      i+←1
                      CURRFILES,←⊂url
                      desc←∊desc,file Type⊃Words ctrlsec
                      item←('#nodeS',⍕i)'.listitem'out.Add _.p desc
                      item.On'click contextmenu' 'OnSample'
                  :EndIf
              :EndFor
          :EndFor
          :If ~×i
              '.noitems'out.Add _.p,⊂'[no results for ',str,']'
          :EndIf
          title←(⍕≢CURRFILES),' sample pages for ',Q str
          desc←'Click on a button below to select a sample.'
          r←(Q str)title desc out''
          SEARCH←GenJS⍣(STR≡'')⊢r ⍝ Do not finish up if called by OnTree
          STR←'' ⍝ Reset
      :EndIf
      r←SEARCH
    ∇

    ∇ (page title desc iframe code)←{spacectrl}Update url;ctrlsec;control;space
     ⍝ Create new placeholder values
      iframe←Frame url
      code←Dread url
      :If ×⎕NC'spacectrl'
          page←spacectrl
          (space control)←spacectrl⊂⍨¯1↓1,spacectrl∊'.'
          title←NewWinA(space,AddShortInfo⊂control)url

          desc←#.HTMLInput.dtlb'Description'Section code
          (⊃desc)←⎕SE.Dyalog.Utils.lcase⊃desc
          desc,⍨←(4↓⊃AddLongInfo,⊂control),' The following example uses the ',control,' element in order to '
          desc,←'.'
      :Else
          page←'Sample'
          ctrlsec←'Control'Section code
          title←NewWinA ctrlsec url
          desc←'Description'Section code
      :End
    ∇

    ∇ r←GenJS(page title desc iframe code)
     ⍝ Generate JavaScript for filling placeholders
      r←'#title'Replace'MS3: ',page
      r,←'#SampleTitle'Replace title
      r,←'#SampleDesc'Replace desc
      r,←'#SampleFrame'Replace iframe
      r,←'#SampleSource'Replace(×≢code)/'x-small;border:none'#.HTMLInput.APLToHTMLColour code
    ∇

    ∇ node←GetNode
      node←4↓(1+'tree'≡4↑_what)⊃_what(Get'node')
    ∇
    :ENDSECTION

:EndClass

:Class index : MiPageSample

    :SECTION GLOBALS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    :Field TYPES←'Simple' 'Advanced'⍝ Types of samples (if not App)
    :Field GROUPS      ⍝ Names of groups of elements
    :Field REFS        ⍝ ... their refs
    :Field APPS        ⍝ List of all apps
    :Field SAMPLES     ⍝ List of all samples (well, groups and controls really)
    :Field INFOLONG    ⍝ Long desc of each control
    :Field INFOSHORT   ⍝ Short (1-3 words) desc of each control
    :Field NAMESLONG   ⍝ Elements that have long info
    :Field NAMESSHORT  ⍝ Elements that have short info
    :Field SEARCH←''   ⍝ Last-search cache
    :Field CONTROLS←'' ⍝ All control names
    :Field APPDESCS    ⍝ Sample Apps' Description::
    :Field APPCTRLS    ⍝ Sample Apps' Control:: (i.e. the title)
    :Field CORE        ⍝ Core subset of controls
    :Field STR←''      ⍝ String to search for

    :ENDSECTION

    :SECTION UTILITIES ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    Q←'"'∘,,∘'"'  ⍝ Surround with quotes, a.k.a. APL dragon

    D←{(×≢⍵)/' – ',⍵} ⍝ Prepend a dash if non-empty

    P←{1⌽(×≢#.HTMLInput.dtlb ⍵)/') (',⍵} ⍝ Surround with parens if non-empty

    Words←{(1↓¨(' '∘=⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces

    SpaceToDir←{'Examples/',1↓⍵} ⍝ Convert Space name to Folder name

    Frame←{'.iframed' ('src=',Q⍵,'?NoWrapper=1') New _.iframe}

    NewWinA←{('target' '_blank')('href=',Q⊃⌽⍵) New _.a (⊃⍵)} ⍝ New link that opens in a new window/tab

    NewDiv←{⍵ New _.div} ⍝ Make a div

    Horz←{⍺←⊢ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1} ⍝ Horizontal StackPanel

    Type←{P(1⍳⍨∨/¨TYPES⍷¨⊂⍺)⊃(TYPES,¨⊂' sample for ',⍵),⊂'Sample app'} ⍝ Simple/Advanced/App?

    Dread←{0::,⊂'[file read failed]' ⋄ #.UnicodeFile.ReadNestedText #.Boot.AppRoot,⍵,'.dyalog'} ⍝ Read dyalog file

    Dlist←{0::⍬ ⋄ ¯7↓¨6⊃#.Files.DirX #.Boot.AppRoot,⍵,'/*.dyalog'} ⍝ List dyalog files

    NodeID←{⍺←⊢ ⋄ ('#node',⊃⍵)∘,¨⍕¨⍺+⍳⍴⊃⌽⍵} ⍝ Generate node ids (⍵='L' items) optional (⍺=offset)

    Split←{{⍵↑⍨+/∨\' '≠⌽⍵}¨('_'⎕R' ')⍵((⍳↑⊣){⍺⍵}(⍳↓⊣))'_'} ⍝ Split at first _ and fix _s

    In←{∨/¨⊃⍷¨/{⎕SE.Dyalog.Utils.lcase¨eis ⍵}¨⍺ ⍵} ⍝ Case-insensitive find

    Names←{1↓¨⍵↑¨⍨¯1+⍵⍳¨':'} ⍝ Extract section names from set of several 'name:: description'

    AddShortInfo←{⍵,¨P¨(4+⍴¨⍵)↓¨(INFOSHORT,⊂'')[NAMESSHORT⍳⍵]} ⍝ ⍵ (ShortDesc)

    AddLongInfo←{(New¨_.strong,¨⍵),¨D¨(4+⍴¨⍵)↓¨(INFOLONG,⊂'')[NAMESLONG⍳⍵]} ⍝ ⍵ - LongDesc

    ScoreIn←{(100⊥⌽∨/¨TYPES⍷¨⊂⍵)×(⊂⍺)(∊×⍳⍨)Words'Control'Section Dread ⍵} ⍝ ⍺'s relevance in ⍵ (0=none 1=high 2=lower...)

      Section←{ ⍝ extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)'
          opts←('Mode' 'M')('DotAll' 1)
          res←(regex ⎕S'\1\2'⍠opts)⍵
          (1↓⊃res)~'⍝'
      }

      JSsafe←{ ⍝ Make text JS safe by escaping problematic chars
          fr←'''' '"' '\\' '\n' '\r',⎕UCS 9 8 12               ⍝ ' " \ NL CR TB BS FF
          to←'\\''' '\\"' '\\\\' '\\n' '\\r' '\\t' '\\b' '\\f' ⍝ problematic in regex too :-)
          Q(fr ⎕R to⍠'Mode' 'D')⍵ ⍝ Replace problematic chars and sourround with quotes
      }

    ∇ js←APLtoJS vtv;act;data
      :Access public shared
     ⍝ Convert vectors of pseudo JS (generated by Replace, Execute, et al.) to proper JS

      js←''
      :For act data :In eis vtv
          data←JSsafe⊃⌽eis data ⍝ the last or only one
          :Select ⊃eis act      ⍝ the first or only one
          :Case 'assign'
              js,←(⊃⌽act),' = ',data,';' ⍝ variable = "data"
          :Case 'execute'
              js,←'eval(',data,');' ⍝ eval("data")
          :Case 'replace'
              js,←'$("',(⊃⌽act),'").html(',data,');' ⍝ $("#myid").html("data")
          :Else
              js,←'$("',(⊃⌽act),'").',(⊃act),'(',data,');' ⍝ $("#myid").append("data")
          :EndSelect
      :EndFor
    ∇

    :ENDSECTION

    :SECTION MAIN ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ Compose;left;mid;right;sp;style
      :Access public

     ⍝ Initialize globals
      SAMPLES←0 3⍴⍬
      GROUPS←'Dyalog Controls' 'Base HTML' 'SyncFusion' 'JQueryUI'
      REFS←_DC _html _SF _JQ
      INFOSHORT←'⍝',¨Dread'Examples/Data/infoShort'
      INFOLONG←'⍝',¨Dread'Examples/Data/infoLong'
      NAMESSHORT←Names INFOSHORT
      NAMESLONG←Names INFOLONG

      '#title'Add _.title'MS3: About' ⍝ After the : will be updated

      Add _.StyleSheet'/Styles/homepage.css'
      style←''
      style,←'#leftBar {background-color:inherit;margin-right:6px;} '
      style,←'.menu {padding-left,padding-right:0;} '
      style,←'.cat {font-size:12pt;cursor:pointer;border:1px solid transparent;padding:0 4px 2px 4px;} '
      style,←'.cat:hover {background:linear-gradient(to bottom,#ffbb60 0%,#f37603 100%);border:1px solid #f9cb59;font-weight:bold;} '
      style,←'.menuitem {margin-bottom,margin-left:0px;padding-left:2px;cursor:pointer;} '
      style,←'.menuitem:hover {background-color:highlight;} '
      style,←'.framed {width:730px;max-height:600px;min-height:400px;border-left:1px inset;overflow-y:auto;} '
      style,←'.iframed {width:730px;max-height:600px;min-height:400px;border:2px inset;overflow-y:auto;background-color:white;} '
      style,←'.listitem {margin:0px;padding:4px 4px 4px 26px;text-indent:-22px;cursor:pointer;box-sizing:border-box;} '
      style,←'.listitem:before {content:"▶";padding: 1px 3px 3px 4px;'
      style,←'    background: ThreeDFace;'
      style,←'    color: ButtonText;'
      style,←'    border-radius: 4px;'
      style,←'    border: solid 1px ThreeDLightShadow;'
      style,←'    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);'
      style,←'    transition-duration: 0.2s;'
      style,←'    margin-right: 4px;'
      style,←'}'
      style,←'.listitem:active:before, #SampleTitle a:active:after {'
      style,←'    box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);'
      style,←'    background: ThreeDShadow;'
      style,←'    border: solid 1px ThreeDDarkShadow;'
      style,←'}'
      style,←'.listitem:hover:before, #SampleTitle a:hover:after {'
      style,←'    background: ThreeDHighlight;'
      style,←'    border: solid 1px ThreeDFace;'
      style,←'    text-decoration: none;'
      style,←'}'
      style,←'.noitems {margin:0px;padding:4px;cursor:not-allowed;} '
      style,←'.samplesource {overflow-x:auto;width:730px;background-color:#e5e5cc;border:2px inset;} '
      style,←'#SampleTitle a:hover {text-decoration: none}'
      style,←'#SampleTitle a:after {content:"➚";padding: 0px 4px;'
      style,←'    background: ThreeDFace;'
      style,←'    color: ButtonText;'
      style,←'    border-radius: 4px;'
      style,←'    border: solid 1px ThreeDLightShadow;'
      style,←'    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);'
      style,←'    transition-duration: 0.2s;'
      style,←'    margin-left: 8px;'
      style,←'    font-weight: normal;'
      style,←'}'
      Add _.style style

      (left mid)←NewDiv¨'#leftBar' '#midBar' ⍝ Create panes
      sp←Add'mainSP'Horz left mid

      sp.Items[1].style←⊂'width: 200px; max-height: 450px;'
      sp.Items[2].style←⊂'margin: 5px;'
      sp.style←'height: 450px;background-color:inherit;'

      PopulateLeft left
      PopulateMid mid
    ∇

    ∇ PopulateLeft thediv;class;depths;group;items;ref;samples;vp;search;style;menu;i;fs;ac;text;stuff;treeall;treecore;names;tree

      stuff←''
      tree←0 3⍴0 '' ''

         ⍝ SEARCH FIELD ⍝⍝⍝
      stuff,←New _.EditField'searchfield'⍝).On'change' 'OnSearch'('str' 'val')
      (stuff,←'#searchbutton' '.menu'New _.button'Search').On'click' 'OnSearch'('str' '#searchfield' 'val')

      thediv.Add Horz stuff
      thediv.Add _.hr

      ⍝ SAMPLE APPS ⍝⍝⍝
      APPS←(Dlist'Examples/Apps')~⊂'index'
      APPDESCS←(⊂'Description')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      APPCTRLS←(⊂'Control')Section¨Dread¨'Examples/Apps/'∘,¨APPS
      ('.cat'thediv.Add _.span'Sample Apps').On'click' 'OnAppHeader'
      APPS{tree⍪←1 ⍵('nodeA',⍺)}¨APPCTRLS
      tree←'#treeA'thediv.Add _.ejTreeView tree
      tree.On'nodeSelect' 'OnApp'('node' 'eval' 'argument.id')

      thediv.Add _.hr

      ('#node01All' '.cat'thediv.Add _.span'Controls').On'click' 'OnTree'
      CORE←⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/core -noname'
      treecore←1 3⍴1 'Core' 'node0_Core'
      treeall←1 3⍴1 'All' 'node00All'

      :For group class :InEach GROUPS(3↓¨⍕¨REFS) ⍝ Remove leading #._
          names←⎕THIS.⎕SE.SALT.Load #.Boot.AppRoot,'Examples/Data/tree',class,' -noname'
          CONTROLS,←⊂names[;2]/⍨(⊢=⌈/)names[;1]
          ⍎class,'←names'

          :If ∨/names[;2]∊CORE
              treecore⍪←2(group,P'_',class)('node__',class)
              treecore⍪←3,0 1↓names⌿⍨names[;2]∊CORE
          :EndIf

          treeall⍪←2(group,P'_',class)('node_',class)
          treeall⍪←⍎class

      :EndFor

      tree←'#treeC' 'style="max-height:260px"'thediv.Add _.ejTreeView,⊂treecore⍪treeall
      tree.On'nodeSelect' 'OnTree'('node' 'eval' 'argument.id')
    ∇

    ∇ PopulateMid mid;url;code;frame;mya

     ⍝ Read framed pages
      url←'Examples/Apps/About'
      code←Dread url
     ⍝ Create and fill placeholder for title header
      mya←('#SampleTitle'mid.Add _.h2).Add NewWinA('Control'Section code)url
     ⍝ Create and fill placeholder for description line
      '#SampleDesc'mid.Add _.p('Description'Section code)
     ⍝ Create and fill placeholder for embedded page
      frame←mid.Add NewDiv'#SampleFrame'
      frame.Add Frame url
      '#SampleSource' '.samplesource'mid.Add _.div⍝,⊂'x-small;border:none'#.HTMLInput.APLToHTMLColour code ⍝ No source on page load
    ∇

    :ENDSECTION

    :SECTION CALLBACKS ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ r←OnTree;node;descs;out;items;spacectrl;control;spacedir;files;file;i;url;code;ctrlsec;desc;item;title;currctrls;core;names;class;group;cat;score
      :Access Public
      node←GetNode
      :If '0'∊node ⍝ Core or All
          core←'_'=2⊃node
          currctrls←(CORE∘∩⍣core)⊃,/CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽core↓node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←(2↓node)(2↓node,' Controls')((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃node ⍝ Class or core subset thereof
          core←'_'=2⊃node
          node↓⍨←core ⍝ Remove additional _ from '__html' if core-only
          i←REFS⍳⍎node
          currctrls←(CORE∘∩⍣core)i⊃CONTROLS ⍝ Filter if core-only
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',1⌽core↓node)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←node((i⊃GROUPS),P node)((⍕⍴currctrls),(core/' core'),' controls')out''
      :ElseIf '_'=⊃⌽node ⍝ Category
          (class cat)←Split node
          names←⍎class
          i←(names[;3]/⍨(⊢=⌊/)names[;1])⍳⊂'node',node
          currctrls←1↓i⊃names[;2]⊂⍨(⊢=⌊/)names[;1]
          group←GROUPS⊃⍨REFS⍳⍎'_',class
          out←NewDiv'.framed'
          descs←AddLongInfo currctrls
          items←(currctrls,⍨¨⊂'list',class)out.Add¨_.p,¨descs ⍝ make IDs like 'DC_Button'
          items.Set⊂'.listitem'
          items.On⊂'click' 'OnTree'
          r←('_',class)(group,(P'_',class),' ',cat)(' controls',⍨⍕⍴currctrls)out''
      :Else ⍝ Control
          spacectrl←'_',('_'⎕R'.')node
          (class control)←Split node
          spacedir←'Examples/',class
          files←Dlist spacedir
          out←NewDiv'.framed'
          CURRFILES←0 2⍴0 'url' ⍝ Score URL
          :For file :In files
              url←spacedir,'/',file
              CURRFILES⍪←(spacectrl ScoreIn url)url
          :EndFor
          CURRFILES⌿⍨←×CURRFILES[;1] ⍝ Weed out 0-score samples
          :If 0∊⍴CURRFILES ⍝ Failed to find a sample, so perform a search instead
              STR←spacectrl
              r←OnSearch
              :Return
          :Else
              CURRFILES←CURRFILES[⍋CURRFILES[;1];2] ⍝ Sort by score
              r←spacectrl Update⊃CURRFILES
          :EndIf
      :EndIf
      r←GenJS r
    ∇

    ∇ r←OnAppHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      descs←APPCTRLS,¨' – '∘,¨APPDESCS
      out←NewDiv'.framed'
      items←APPS{('nodeB',⍺)out.Add _.p ⍵}¨descs
      items.Set⊂'.listitem'
      items.On⊂'click' 'OnApp'
      r←GenJS'Apps' 'Small applications to showcase MiServer 3.0 functionality'(' apps',⍨≢APPS)out''
    ∇

    ∇ r←OnCtrlHeader;descs;out;items
      :Access Public
     ⍝ Gets called upon clicking the "Sample Apps" header
      r←GenJS'Controls' 'Something about controls' 'Controls' 'list of all controls with long descs' ''
    ∇

    ∇ r←OnApp
      :Access Public
     ⍝ Gets called upon selection in Sample Apps tree
      r←GenJS Update'Examples/Apps/',1↓GetNode
    ∇

    ∇ r←OnSample
      :Access Public
      :Select _event
      :Case 'click'
          r←GenJS Update CURRFILES⊃⍨⍎5↓_what ⍝ id="nodeS23"
      :Case 'contextmenu'
          r←Execute'window.open("','");',⍨CURRFILES⊃⍨⍎5↓_what ⍝ id="nodeS23"
      :EndSelect
    ∇

    ∇ r←OnSearch;str;terms;out;i;dir;files;file;url;code;ctrlsec;desc;item;title
      :Access Public
      :If ×≢str←GetStr  ⍝ get search string
      ⍝⍝⍝ Controls
          terms←INFOSHORT/⍨str In INFOSHORT
          terms,←terms~⍨INFOLONG/⍨str In INFOLONG
          terms←'.',¨∪Names terms
      ⍝⍝⍝ Samples
          out←NewDiv'.framed'
          CURRFILES←''
          i←0
          :For dir :In 'Apps' 'html' 'HTMLplus' 'DC' 'JQ' 'SF'
              files←Dlist'Examples/',dir
              :For file :In files~⊂'index'
                  url←'Examples/',dir,'/',file
                  code←Dread url
                  ctrlsec←'Control'Section code
                  desc←⊂'Description'Section code
                  :If ∨/str In desc
                  :OrIf ∨/∊(terms,⊂str)In¨⊂ctrlsec ⍝ always case sensitive
                      i+←1
                      CURRFILES,←⊂url
                      desc←∊desc,file Type⊃Words ctrlsec
                      item←('#nodeS',⍕i)'.listitem'out.Add _.p desc
                      item.On'click contextmenu' 'OnSample'
                  :EndIf
              :EndFor
          :EndFor
          :If ~×i
              '.noitems'out.Add _.p,⊂'[no results for ',str,']'
          :EndIf
          title←(⍕≢CURRFILES),' sample pages for ',Q str
          desc←'Click on a button below to select a sample.'
          SEARCH←(Q str)title desc out''
      :EndIf
      r←GenJS SEARCH

    ∇

    ∇ (page title desc iframe code)←{spacectrl}Update url;ctrlsec;control;space
     ⍝ Create new placeholder values
      iframe←Frame url
      code←Dread url
      :If ×⎕NC'spacectrl'
          page←spacectrl
          (space control)←spacectrl⊂⍨¯1↓1,spacectrl∊'.'
          title←NewWinA(space,AddShortInfo⊂control)url

          desc←#.HTMLInput.dtlb'Description'Section code
          (⊃desc)←⎕SE.Dyalog.Utils.lcase⊃desc
          desc,⍨←(4↓⊃AddLongInfo,⊂control),' The following example uses the ',control,' element in order to '
          desc,←'.'
      :Else
          page←'Sample'
          ctrlsec←'Control'Section code
          title←NewWinA ctrlsec url
          desc←'Description'Section code
      :End
    ∇

    ∇ r←GenJS(page title desc iframe code)
     ⍝ Generate JavaScript for filling placeholders
      r←'#title'Replace'MS3: ',page
      r,←'#SampleTitle'Replace title
      r,←'#SampleDesc'Replace desc
      r,←'#SampleFrame'Replace iframe
      r,←'#SampleSource'Replace(×≢code)/'x-small;border:none'#.HTMLInput.APLToHTMLColour code
    ∇

    ∇ node←GetNode
      node←4↓(1+'tree'≡4↑_what)⊃_what(Get'node')
    ∇
    :ENDSECTION

    ∇ str←GetStr
      str←(1+STR≡'')⊃STR(Get'str')
      STR←''
    ∇
:EndClass


Update ejTabAdvanced.dyalog

Update ejTabAdvanced.dyalog

_ was missing in some Add-statements

:Class ejTabAdvanced : MiPageSample
⍝ Control:: _SF.ejTab
⍝ Description:: Add an advanced tabbed interface

    ∇ Compose;captions;contents;intro;tab
      :Access public

      intro←'Frequently used features include the ability to switch between four orientations '
      intro,←'and load content asynchronously from another page.'

      Add _.title'_SF.ejTab - Simple Example '
      Add _.h2'_SF.ejTab - Syncfusion Tab Control'
      Add _html.p intro

      Add _.h2'Tabs on the left'
      captions←'Tab One' 'Another' 'Third Tab'
      contents←⊂'/Examples/Data/SampleText1.txt' ⍝ First one
      contents,←'These
are
the
contents
of
the
tab
labelled

'∘,¨1↓captions
      tab←Add _.ejTab(captions contents)
      'headerPosition'tab.Set'left'
      tab.IsURL←1 0 0 ⍝ Let it know which are URLs
    ∇

:EndClass

:Class ejTabAdvanced : MiPageSample
⍝ Control:: _SF.ejTab
⍝ Description:: Add an advanced tabbed interface

    ∇ Compose;captions;contents;intro;tab
      :Access public

      intro←'Frequently used features include the ability to switch between four orientations '
      intro,←'and load content asynchronously from another page.'

      Add title'_SF.ejTab - Simple Example '
      Add h2'_SF.ejTab - Syncfusion Tab Control'
      Add _html.p intro

      Add h2'Tabs on the left'
      captions←'Tab One' 'Another' 'Third Tab'
      contents←⊂'/Examples/Data/SampleText1.txt' ⍝ First one
      contents,←'These
are
the
contents
of
the
tab
labelled

'∘,¨1↓captions
      tab←Add #._SF.ejTab(captions contents)
      'headerPosition'tab.Set'left'
      tab.IsURL←1 0 0 ⍝ Let it know which are URLs
    ∇

:EndClass


Syncfusion requires <a> tag on items even if they have no link

:class ejMenu : #._SF._ejWidget
⍝ Description:: Syncfusion Menu widget
⍝ Constructor:: [items [levels [links]]]
⍝ items   - vector of char vectors containing the menu item caption
⍝ levels  - the level of the corresponding item
⍝ links   - the HREF string to be used when the corresponding element is chosen (clicked)
⍝ Public Fields::
⍝ Items   - vector of char vectors containing the menu item caption
⍝ Levels  - the level of the corresponding item
⍝ Links   - the HREF string to be used when the corresponding element is chosen (clicked)
⍝ MakeIds - 1 makes ids in the format myid_1_2

    :field public shared readonly DocBase←'http://help.syncfusion.com/js/api/ejMenu.html'
    :field public shared readonly ApiLevel←3
    :field public shared readonly DocDyalog←'/Documentation/DyalogAPIs/Syncfusion/ejMenu.html'
    :field public Levels←1
    :field public Items←⍬
    :field public Links←⍬
    :field public MakeIds←0

    ∇ make
      :Access public
      JQueryFn←Uses←'ejMenu'
      ContainerTag←'ul'
      (Items Levels Links)←⊂⍬
      :Implements constructor
    ∇

    ∇ make1 args
      :Access public
      :Implements constructor
      JQueryFn←Uses←'ejMenu'
      ContainerTag←'ul'

      :Select ⊃⍴⍴args ⍝ Select on Rank
      :Case 1         ⍝ Vector
          :If 0 2∊⍨10|⎕DR⊃args ⍝ 1st element is simple char
              args←,⊂args
          :EndIf      ⍝ Matrix
      :Case 2 ⋄ args←↓[1]args ⍝ Split columns
      :Else
      :EndSelect
      args←eis args
      (Items Levels Links)←args defaultArgs Items Levels Links
    ∇

    ∇ {r}←AddItem args
      :Access public ⍝ Obs: max 1
      (Items Levels Links),←args defaultArgs'' 1 ''
    ∇

    ∇ r←Render;i;link;links;opt;opts;text;n;ids;mat;diff;ul;xp;placeholders;from;to
      :Access public
      Tag←'ul'
      SetId

      n←≢Items

      :If ~0∊n
          opts←{'⍎'=⊃⍵:1↓⍵ ⋄ isRef ⍵:⍵ ⋄ HtmlSafeText ⍵}¨eis Items
          links←(⍴Items)↑(eis Links),(⍴Items)⍴⊂''
          text←opts
          opts←⍬
          :For i link :InEach (⍳⍴links)links
              :If ''≡link ⍝ no link
                  opt←New _.a(i⊃text)
              :ElseIf 326=⎕DR link ⍝ Nested array
                  (opt←(⊂'href'(⊃link))New _.a(i⊃text)).Set 1↓link
              :Else ⍝ no attrs
                  opt←(⊂'href'link)New _.a(i⊃text)
              :EndIf
              opts,←opt
          :EndFor

⍝          mat←⎕XML∊{isRef ⍵:⍵.Render ⋄ '',⍵,''}¨opts
          placeholders←⍕¨⍳⍴opts

          mat←0,(⊂'span'),((⍪placeholders),⊂0 2⍴⊂''),5

          Levels⍴⍨←n
          diff←0,¯2-/Levels
          ul←≠\2/diff>0           ⍝ Step up: new ul

          ids←{'id'⍵}¨id #.Utils.levels2ids Levels

          i←⍋⍋(2×n)⍴0 1
          mat←(,Levels∘.+0 1),(((n 2⍴'li' ''),ids)⍪mat[;1+⍳3])[i;]

     ⍝ We now insert ul/ols where they belong
          xp←~ul←≠\(1+ul)/ul ⋄ n←≢i←ul/⍳⍴ul
          mat←xp⍀mat
          mat[i;]←mat[i-1;1],n 3⍴Tag'' '' ⍝ same as level before
          mat[;1]+←+\xp\,0,⍨⍪diff           ⍝ the others get corrected

     ⍝ Finally we adjust the level number so it starts a 0
          mat[;1]-←1⍴mat

     ⍝ Prepare replacement of placeholders
          from←{'',⍵,''}¨placeholders
          to←{isRef ⍵:⍵.Render ⋄ ⍵}¨opts

     ⍝ Use ⎕XML to format nicely
          Container.Content←from ⎕R to⊢⎕XML mat
      :EndIf

      r←⎕BASE.Render
    ∇

:EndClass

:class ejMenu : #._SF._ejWidget
⍝ Description:: Syncfusion Menu widget
⍝ Constructor:: [items [levels [links]]]
⍝ items   - vector of char vectors containing the menu item caption
⍝ levels  - the level of the corresponding item
⍝ links   - the HREF string to be used when the corresponding element is chosen (clicked)
⍝ Public Fields::
⍝ Items   - vector of char vectors containing the menu item caption
⍝ Levels  - the level of the corresponding item
⍝ Links   - the HREF string to be used when the corresponding element is chosen (clicked)
⍝ MakeIds - 1 makes ids in the format myid_1_2

    :field public shared readonly DocBase←'http://help.syncfusion.com/js/api/ejMenu.html'
    :field public shared readonly ApiLevel←3
    :field public shared readonly DocDyalog←'/Documentation/DyalogAPIs/Syncfusion/ejMenu.html'
    :field public Levels←1
    :field public Items←⍬
    :field public Links←⍬
    :field public MakeIds←0

    ∇ make
      :Access public
      JQueryFn←Uses←'ejMenu'
      ContainerTag←'ul'
      (Items Levels Links)←⊂⍬
      :Implements constructor
    ∇

    ∇ make1 args
      :Access public
      :Implements constructor
      JQueryFn←Uses←'ejMenu'
      ContainerTag←'ul'

      :Select ⊃⍴⍴args ⍝ Select on Rank
      :Case 1         ⍝ Vector
          :If 0 2∊⍨10|⎕DR⊃args ⍝ 1st element is simple char
              args←,⊂args
          :EndIf      ⍝ Matrix
      :Case 2 ⋄ args←↓[1]args ⍝ Split columns
      :Else
      :EndSelect
      args←eis args
      (Items Levels Links)←args defaultArgs Items Levels Links
    ∇

    ∇ {r}←AddItem args
      :Access public ⍝ Obs: max 1
      (Items Levels Links),←args defaultArgs'' 1 ''
    ∇

    ∇ r←Render;i;link;links;opt;opts;text;n;ids;mat;diff;ul;xp;placeholders;from;to
      :Access public
      Tag←'ul'
      SetId

      n←≢Items

      :If ~0∊n
          opts←{'⍎'=⊃⍵:1↓⍵ ⋄ isRef ⍵:⍵ ⋄ HtmlSafeText ⍵}¨eis Items
          links←(⍴Items)↑(eis Links),(⍴Items)⍴⊂''
          text←opts
          opts←⍬
          :For i link :InEach (⍳⍴links)links
              :If ''≡link ⍝ no link
                  opt←text[i]
              :ElseIf 326=⎕DR link ⍝ Nested array
                  (opt←(⊂'href'(⊃link))New _.a(i⊃text)).Set 1↓link
              :Else ⍝ no attrs
                  opt←(⊂'href'link)New _.a(i⊃text)
              :EndIf
              opts,←opt
          :EndFor

⍝          mat←⎕XML∊{isRef ⍵:⍵.Render ⋄ '',⍵,''}¨opts
          placeholders←⍕¨⍳⍴opts

          mat←0,(⊂'span'),((⍪placeholders),⊂0 2⍴⊂''),5

          Levels⍴⍨←n
          diff←0,¯2-/Levels
          ul←≠\2/diff>0           ⍝ Step up: new ul

          ids←{'id'⍵}¨id #.Utils.levels2ids Levels

          i←⍋⍋(2×n)⍴0 1
          mat←(,Levels∘.+0 1),(((n 2⍴'li' ''),ids)⍪mat[;1+⍳3])[i;]

     ⍝ We now insert ul/ols where they belong
          xp←~ul←≠\(1+ul)/ul ⋄ n←≢i←ul/⍳⍴ul
          mat←xp⍀mat
          mat[i;]←mat[i-1;1],n 3⍴Tag'' '' ⍝ same as level before
          mat[;1]+←+\xp\,0,⍨⍪diff           ⍝ the others get corrected

     ⍝ Finally we adjust the level number so it starts a 0
          mat[;1]-←1⍴mat

     ⍝ Prepare replacement of placeholders
          from←{'',⍵,''}¨placeholders
          to←{isRef ⍵:⍵.Render ⋄ ⍵}¨opts

     ⍝ Use ⎕XML to format nicely
          Container.Content←from ⎕R to⊢⎕XML mat
      :EndIf

      r←⎕BASE.Render
    ∇

:EndClass