Boot now uses protected SALT.Load.

Boot now uses protected SALT.Load.

:Namespace Boot

    (⎕ML ⎕IO)←1

    :section Startup/Shutdown

    ∇ Run root

      AppRoot←folderize root  ⍝ application (website) root
      AppRoot Load 1 ⍝ load essential objects
      #.Utils.SetupCompatibility
      ms←Init ConfigureServer AppRoot ⍝ read configuration and create server instance
      ConfigureDatasources ms
      ConfigureVirtual ms
      ConfigureResources ms
      ConfigureContentTypes ms
      ConfigureLogger ms

      ms.Run
    ∇

    ∇ {AppRoot}Load yes;files;f;classes;class;utils;t;core;HTML;extensions;HTMLsubdirs
      ⍝ Load required objects for MiServer
      ⍝ Note: DRC namespace is not SALTed
      ⍝ yes - 1 to perform load, 0 to clean up

      classes←''
      :If 0≠⎕NC'AppRoot'
          classes←(⎕SE.SALT.List AppRoot,'Code -raw')[;1 2] ⍝ Classes in application folder
          classes←(empty¨classes[;1])/classes[;2] ⍝ remove directory entries (have <DIR> in [;1])
      :EndIf
      utils←(⎕SE.SALT.List MSRoot,'Utils -raw')[;2]   ⍝ find utility libraries
      core←(⊂'Boot')~⍨(⎕SE.SALT.List MSRoot,'Core -raw')[;2]
      extensions←(⎕SE.SALT.List MSRoot,'Extensions -raw')[;2]
      HTML←(⎕SE.SALT.List MSRoot,'HTML -raw')[;2] ⍝ force to be loaded first
      HTMLsubdirs←HTML∩,⎕SE.SALT.List MSRoot,'HTML -folder'
      :If yes

          files←'Core/'∘,¨core
          files,←'Utils/'∘,¨utils
          files,←'Extensions/'∘,¨extensions

          :For f :In files
              disperror 1 ⎕SE.SALT.Load MSRoot,f,' -target=#' ⍝ do not reload already loaded spaces
          :EndFor

          #.SupportedHtml5Elements.Build_html_namespace

          :For f :In HTML
              disperror 1 ⎕SE.SALT.Load MSRoot,'HTML/',f,' -target=#'
              :If (⊂f)∊HTMLsubdirs
                  disperror¨1 ⎕SE.SALT.Load MSRoot,'HTML/',f,'/* -target=#.',f
              :EndIf
          :EndFor

⍝          :For f :In HTML∩,⎕SE.SALT.List MSRoot,'HTML -folder'
⍝              ⎕SE.SALT.Load MSRoot,'HTML/',f,'/* -target=#.',f
⍝          :EndFor

          :If 0≠⍴classes
              :For class :In classes
                  disperror 1 ⎕SE.SALT.Load AppRoot,'Code/',class,' -target=#'
              :EndFor
          :EndIf

          'Pages'#.⎕NS'' ⍝ Container Space for loaded classes
          #.Pages.(MiPage MildPage RESTful)←#.(MiPage MildPage RESTful)
          BuildEAWC ⍝ build the Easy As ⎕WC namespace
          :If #.Files.DirExists AppRoot,'/Code/Templates/'
              disperror 1 ⎕SE.SALT.Load AppRoot,'/Code/Templates/* -target=#.Pages'
          :EndIf

      :Else ⍝ Cleanup
          #.⎕EX¨classes
          #.⎕EX¨utils
          #.⎕EX'Pages'
          #.⎕EX¨'MiServer' 'HTTPRequest'
      :EndIf
    ∇

    ∇ LoadHTML f
    ⍝ Utility to reload an HTML content-generating namespace
      disperror 1 ⎕SE.SALT.Load MSRoot,'HTML/',f,' -target=#'
      disperror¨1 ⎕SE.SALT.Load MSRoot,'HTML/',f,'/* -target=#.',f
    ∇

    ∇ ms←Init Config;path;class;classes;e;res;mask
     ⍝ Create instances of MiServer, Session and Authentication Handlers

      ms←⎕NEW(#⍎Config.ClassName)Config
      path←MSRoot,'Extensions/'

      :If 0≠⍴Config.SessionHandler
          class←1 ⎕SE.SALT.Load path,Config.SessionHandler
          ms.SessionHandler←⎕NEW class ms
      :EndIf

      :If 0≠⍴Config.Authentication
          class←1 ⎕SE.SALT.Load path,Config.Authentication
          ms.Authentication←⎕NEW class ms
      :EndIf

      :If 0≠⍴Config.SupportedEncodings
          {}1 ⎕SE.SALT.Load path,'ContentEncoder'
          :For e :In Config.SupportedEncodings
              class←1 ⎕SE.SALT.Load path,e
              ms.Encoders,←⎕NEW class
          :EndFor
          :If ∨/mask←0≠1⊃¨res←ms.Encoders.Init
              2 ms.Log'Content Encoding Initialization failed for:',∊' ',¨mask/ms.Encoders.Encoding
              ms.Encoders←(~mask)/ms.Encoders
          :EndIf
      :EndIf
      Config.UseContentEncoding∧←0≠⍴ms.Encoders

      :If 0≠⍴Config.Logger
          class←1 ⎕SE.SALT.Load path,Config.Logger
          ms.Logger←⎕NEW class ms
      :EndIf
    ∇

    ∇ End;classes;z;m
      ⍝ Clean up the workspace

      :If 9=⎕NC'ms'
          :Trap 0
              ms.End
          :EndTrap
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.SessionHandler'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Authentication'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Logger'
          {}try'⎕EX⍕¨∪enlist ⎕CLASS¨ms.Encoders'
          ⎕EX⍕⊃⊃⎕CLASS ms
          ⎕EX'ms'
      :EndIf

      :If 9=⎕NC'SQA'
          {}try'SQA.Close''.'''
      :EndIf

      :If 0≠⍴classes←↓#.⎕NL 9.4
      :AndIf 0≠⍴classes←(m←2=⊃∘⍴¨z←⎕CLASS¨#⍎¨classes)/classes
      :AndIf 0≠⎕NC'#.MiPage'
          classes←(#.MiPage≡¨2 1∘⊃¨m/z)/classes
          #.⎕EX↑⍕¨classes ⍝ Erase loaded classes
      :EndIf

      Load 0
      ⎕EX'#.MiPage'
      ⎕EX'AppRoot'
      {}try'#.DRC.Close ''.'''
    ∇

    ∇ BuildEAWC;src;sources;fields;source;list;mask;refs;target
     ⍝ Build the Easy As ⎕WC namespace from core classes and its own source
     ⍝ Also build the #._ namespace with shortcuts
      sources←#._html #._SF #._JQ #._DC #._JS
⍝      fields←''
      '_'#.⎕NS''
      :For source :In sources
          list←source.⎕NL ¯9.4
          list←list/⍨'_'≠⊃¨list
          :If ∨/mask←0≠refs←source{6::0 ⋄ (⍕⍺){('.'∊1↓s↓r)<⍺≡(s←⍴⍺)↑r←⍕⍵}t←⍺⍎⍵:t ⋄ 0}¨list
              #._⍎∊(mask/list){'⋄',⍺,'←',⍕⍵}¨mask/refs
          :EndIf
      :EndFor
    ∇

    :endsection

    :section Configuration

    ∇ r←ns Setting pars;name;num;default;mask
    ⍝ returns setting from a config style namespace or provides a default if it doesn't exist
    ⍝ pars - name [num] [default]
    ⍝ ns - namespace reference
    ⍝ name - name of the setting
    ⍝ num - 1 if setting is numeric scalar, (,1) if numeric vector is allowed, 0 otherwise
    ⍝ default - default value if not found
      pars←eis pars
      (name num)←2↑pars,(⍴pars)↓'' 0 ''
      :If 2<⍴pars ⋄ default←3⊃pars
      :Else ⋄ default←(1+num)⊃''⍬
      :EndIf
      r←(⍴ns)⍴⊂default
      :If ∨/mask←0≠⊃¨ns.⎕NC⊂name
          (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name
      :EndIf
      :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf
    ∇

    ∇ config←{element}ReadConfiguration type;serverconfig;file;siteconfig;thing;ind;mask
    ⍝ Attempt to read configuration file
    ⍝ 1) from server root MSRoot
    ⍝ 2) from site root AppRoot
    ⍝ merging the two if they both exist - site settings overrule server settings
      config←''
      :If #.Files.Exists file←MSRoot,'Config/',type,'.xml'
          config←serverconfig←(#.XML.ToNS #.Files.GetText file)⍎type
      :EndIf
      :If #.Files.Exists file←AppRoot,'Config/',type,'.xml'
          siteconfig←(#.XML.ToNS #.Files.GetText file)⍎type
          :If 0∊⍴config
              config←siteconfig
          :ElseIf 0=⎕NC'element'
              {}{try'serverconfig.',⍵,'←siteconfig.',⍵}¨siteconfig.⎕NL ¯2
          :Else ⍝ element specifies the element(s) to search on
              :For thing :In siteconfig
                  :If 0≠thing.⎕NC element
                      :If ∨/mask←0≠∊serverconfig.⎕NC⊂element
                          :If (+/mask)<ind←((mask/serverconfig)⍎¨⊂element)⍳⊂thing⍎element
                              serverconfig,←thing
                          :Else
                              serverconfig[(mask/⍳⍴mask)[ind]]←thing
                          :EndIf
                      :Else
                          serverconfig,←thing
                      :EndIf
                  :EndIf
              :EndFor
              config←serverconfig
          :EndIf
      :EndIf
    ∇

    ∇ Config←ConfigureServer AppRoot;file
    ⍝ configure server level settings, setting defaults for needed ones that are not supplied
      Config←ReadConfiguration'Server'

      Config.AllowedHttpCommands←{⎕ML←3 ⋄ ⍵⊂⍨~⍵∊' ,'}#.Strings.lc Config Setting'AllowedHttpCommands' 0 'get,post'
      Config.AppRoot←AppRoot
      Config.Authentication←Config Setting'Authentication' 0 'SimpleAuth'
      Config.CertFile←Config Setting'CertFile' 0 ''
      Config.ClassName←Config Setting'ClassName' 0 'MiServer'
      Config.Debug←Config Setting'Debug' 1 0
      Config.DefaultExtension←Config Setting'DefaultExtension' 0 '.mipage'
      Config.DefaultPage←Config Setting'DefaultPage' 0 'index.mipage'
      Config.FormatHtml←Config Setting'FormatHtml' 1 0
      Config.Host←Config Setting'Host' 0 'localhost'
      Config.HttpCacheTime←Config Setting'HttpCacheTime' 1 0 ⍝ default to off (0)
      Config.IPVersion←Config Setting'IPVersion' 0 'IPV4'
      Config.IdleTimeOut←Config Setting'IdleTimeOut' 1 0 ⍝ default to none (0)
      Config.KeyFile←Config Setting'KeyFile' 0 ''
      Config.Lang←Config Setting'Lang' 0 'en'
      Config.LogMessageLevel←Config Setting'LogMessageLevel' 1 1 ⍝ default to error messages only
      Config.Logger←Config Setting'Logger' 0 ''
      Config.MSRoot←MSRoot
      Config.Name←Config Setting'Name' 0 'MiServer'
      Config.Port←Config Setting'Port' 1 8080
      Config.Ports←Config Setting'Ports'(,1)⍬
      Config.Production←Config Setting'Production' 1 0 ⍝ production mode?  (0/1 = development debug framework en/disabled)
      Config.RESTful←Config Setting'RESTful' 1 0 ⍝ RESTful web service?
      Config.RootCertDir←Config Setting'RootCertDir' 0 ''
      Config.Root←AppRoot ⍝ folderize MSRoot{((isRelPath ⍵)/⍺),⍵}AppRoot
      Config.SSLFlags←Config Setting'SSLFlags' 1(32+64)  ⍝ Accept Without Validating, RequestClientCertificate
      Config.Secure←Config Setting'Secure' 1 0
      Config.Server←Config Setting'Server' 0 ''
      Config.SessionHandler←Config Setting'SessionHandler' 0 'SimpleSessions'
      Config.SessionTimeOut←Config Setting'SessionTimeOut' 1 30 ⍝ 30 minute timeout
      Config.SupportedEncodings←{(⊂'')~⍨1↓¨(⍵=⊃⍵)⊂⍵}',',Config Setting'SupportedEncodings' 0
      Config.TempFolder←folderize Config.Root{0∊⍴⍵:⍵ ⋄ ((isRelPath ⍵)/⍺),⍵}Config Setting'TempFolder' 0
      Config.TrapErrors←Config Setting'TrapErrors' 1 0
      Config.UseContentEncoding←Config Setting'UseContentEncoding' 1 0 ⍝ aka HTTP Compression default off (0)

      :If 0≠⎕NC'#.DrA' ⍝ Transfer DrA config options
          {}#.DrA.SetDefaults
          #.DrA.AppName←Config.Name
          #.DrA.MailMethod←Config Setting'MailMethod' 0 'NONE'
          #.DrA.MailRecipient←Config Setting'MailRecipient' 0
          #.DrA.Mode←Config.Debug
          #.DrA.NoUser←Config Setting'NoUser' 1 1 ⍝ run without user interaction
          #.DrA.Path←AppRoot ⍝ Where to put log files
          #.DrA.SMTP_Gateway←Config Setting'SMTP_Gateway' 0
          #.DrA.UseHTTP←Config Setting'UseHTTP' 1 0
      :EndIf
    ∇

    ∇ ConfigureDatasources ms;file;ds;name;tmp;orig;dyalog
      ⍝ load any datasource definitions
      :If ~0∊⍴ms.Datasources←'Name'ReadConfiguration'Datasources'
          :For ds :In ms.Datasources
              :For name :In ds.⎕NL ¯2
                  orig←tmp←ds.⍎name
                  tmp←SubstPath tmp
                  :If orig≢tmp
                      ⍎'ds.',name,'←tmp'
                  :EndIf
              :EndFor
          :EndFor

          :Trap 0
              :If 0=#.⎕NC'SQA'
                  dyalog←2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'
                  'SQA'#.⎕CY dyalog,'\ws\sqapl' ⍝ copy in SQA
              :EndIf
              :If 0≠1⊃#.SQA.Init'' ⍝ and initialize
                  1 ms.Log'SQA failed to initialize'
              :EndIf
          :Else
              1 ms.Log'SQA failed to initialize'
          :EndTrap
      :EndIf
    ∇

    ∇ ConfigureVirtual ms;file;virtual;mask;inds;v
      ⍝ load virtual (alias) definitions if any
      ms.Config.Virtual←''
      :If ~0∊⍴virtual←'alias'ReadConfiguration'Virtual'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}virtual.alias ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate virtual aliases defined (first occurrence will be used): ',1↓enlist',',¨(~mask)/virtual.alias
              virtual←mask/virtual
          :EndIf
          ⍝ substitute server and site root if found
          virtual.path←SubstPath¨virtual.path
          :For v :In virtual
              :If ~#.Files.DirExists v.path ⍝ check if mapped path exists
                  1 ms.Log'Virtual path not found: ',v.path
                  virtual~←v
              :Else
                  :If #.Files.DirExists AppRoot,v.alias ⍝ check if alias conflicts with local path
                      1 ms.Log'Virtual alias "',v.alias,'" overrides site path of same name.'
                  :EndIf
              :EndIf
          :EndFor
          ms.Config.Virtual←virtual
      :EndIf
    ∇

    ∇ ConfigureResources ms;file;mask;inds;names;uses;map;n;res;f;missing;order
      ⍝ load resource definitions if any
      ms.Config.Resources←0 3⍴⊂''
      :If ~0∊⍴res←'name'ReadConfiguration'Resources'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}names←res.name ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate resources defined (first occurence will be used): ',1↓enlist',',¨(~mask)/res.name
              res←mask/res
          :EndIf
          ⍝ build the dependency map
          uses←{(eis⍣(⊃0<⍴⍵))⍵}¨res Setting'uses'
          inds←res.name∘⍳¨uses
          n←⊃⍴res
          :If ∨/mask←∨/¨missing←n<inds
              1 ms.Log'Invalid resource dependency found for:',enlist' ',¨mask/res.name
              inds←(~missing)/¨inds
          :EndIf
          order←OrderResources inds
          inds←(order∘⍳¨inds)[order]
          res←res[order]
          map←,(2⍴n)⍴(1+n)↑1 ⍝ identity matrix
          map[enlist(n×¯1+⍳n)+¨inds]←1
          map←(2⍴n)⍴map
          map←↓{({⍺∨⍺∨.∧⍵}⍣≡)⍨⍵}map
          ⍝ ms.Config.Resources[;1] resource name, [;2] scripts used [;3] styles used
          f←{SubstPath¨{⍵~⊂''}∘∪¨⊃¨,/¨map/¨⊂eis¨⍵}
          ms.Config.Resources←res.name,(f res Setting'script'),[1.1]f res Setting'style'
      :EndIf
    ∇

    ∇ r←OrderResources inds;n;mask
      n←⍳⍴inds
      r←n/⍨mask←0∘∊∘⍴¨inds ⍝ roots with no dependencies
      inds/⍨←~mask
      n/⍨←~mask
      :While ~0∊⍴inds
          mask←∧/¨inds∊¨⊂r
          r,←mask/n
          inds/⍨←~mask
          n/⍨←~mask
      :EndWhile
    ∇

    ∇ ConfigureContentTypes ms;file;ct;inds;exts;types;mask;exp;n
      ⍝ load supported content types
      ms.Config.ContentTypes←0 2⍴⊂''
      :If ~0∊⍴ct←ReadConfiguration'ContentTypes'
          exts←#.Strings.lc¨ct Setting'ext'
          types←ct Setting'type'
          :If ~0∊⍴inds←{⍵/⍳⍴⍵}enlist','∊¨exts
              mask←(⍴exts)⍴1
              exp←{⎕ML←3 ⋄ (⍵≠',')⊂⍵}¨exts[inds]
              mask[inds]←n←⍬∘⍴∘⍴¨exp
              (exts types)←mask∘/¨exts types
              exts[(n/inds)++\~enlist n↑¨1]←⊃,/exp
          :EndIf
          ms.Config.ContentTypes←exts,[1.1]types
      :Else
          1 ms.Log'No content types defined?'
      :EndIf
    ∇

    ∇ ConfigureLogger ms;file;log
      ⍝ load logger information
      ms.Config.Logger←⎕NS''
      ms.Config.Logger.active←0
      :If ~0∊⍴log←ReadConfiguration'Logger'
          ms.Config.Logger.active←log Setting'active' 1 0
          ms.Config.Logger.directory←SubstPath log Setting'directory' 0 ''
          ms.Config.Logger.frequency←log Setting'frequency' 0 ''
          ms.Config.Logger.sizelimit←log Setting'sizelimit' 1 0
      :EndIf
    ∇

    :endsection

    :section Utilities
    disperror←{}∘{326=⎕DR ⍵:'' ⋄ '***'≡3↑⍵:⎕←⍵ ⋄ ''}
    isWin←('.' ⎕WG 'APLVersion')[3]≡⊂,'W'
    fileSep←'/\'[1+isWin]
    MSRoot←{(1-⌊/'/\'⍳⍨⌽⍵)↓⍵}⎕WSID
    isRelPath←{{~'/\'∊⍨(⎕IO+2×isWin∧':'∊⍵)⊃⍵}3↑⍵}
    enlist←{⎕ML←1⋄∊⍵}
      tonum←{⍺←0
          1∊⍺:tonumvec ⍵
          w←⍵ ⋄ ((w='-')/w)←'¯'
          ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w}
    try←{0::'' ⋄⍎⍵}
    empty←{0∊⍴⍵}
    notEmpty←~∘empty
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    isRef←{(0∊⍴⍴⍵)∧326=⎕DR ⍵}
    folderize←{¯1↑⍵∊'/\':⍵ ⋄ ⍵,fileSep}
    makeSitePath←{⍺{((isRelPath ⍵)/⍺),⍵},folderize ⍵}

    ∇ r←tonumvec v;to;minus;digits;c;mask
    ⍝ tonum vector version
    ⍝ allows for specific of ranges and comma or space delimited numbers
    ⍝ tonumvec '8080-8090'  or '5,7-9,11-15'
      r←⍬
      ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯'
      to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵}
      v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v
      minus←'-'=v
      digits←v∊⎕D,'.'
      ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯'
      ((' '=v)/v)←','
      (('-'=v)/v)←⊂' to '
      :Trap 0
          :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v
              r,←⍎∊c
          :EndFor
      :Else
          ⎕SIGNAL 11
      :EndTrap
    ∇

    ∇ r←SubstPath r
      r←(#.Strings.subst∘('%ServerRoot%'(¯1↓MSRoot)))r
      r←(#.Strings.subst∘('%SiteRoot%'(¯1↓AppRoot)))r
    ∇

    ∇ r←isRunning
      :Trap r←0
          r←ms.TID∊⎕TNUMS
      :EndTrap
    ∇

    ∇ r←Oops;dmx;ends;xsi
    ⍝ debugging framework to bubble up to user's code when rendering fails
      r←'⎕SIGNAL 811'
      ends←{(,⍺)≡(-⍴,⍺)↑⍵}
      :If {0::0 ⋄ #.HtmlPage∊∊⎕CLASS ⍵}⊃⊃⎕RSI
          r←'⎕TRAP←(800 ''C'' ''→FAIL'')(811 ''E'' ''⎕SIGNAL 801'')(813 ''E'' ''⎕SIGNAL 803'')(812 ''S'')(85 ''N'')(0 ''S'')'
          ⎕←''
          ⎕←'*** MiServer Debug ***'
          ⎕←↑⎕DMX.DM
          ⎕←''
          ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
          ⎕←'      or Press Ctrl-Enter to invoke debugger'
      :Else
          :Select ⎕DMX.EN
          :Case 801
              xsi←⎕XSI
              :If '.HandleMSP'ends 2⊃xsi
                  r←'→FAIL'
              :ElseIf '.HandleMSP'ends 4⊃xsi
                  :If '.Render'ends 3⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :ElseIf '.HandleMSP'ends 3⊃xsi
                  :If '.Wrap'ends 2⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :Else
                  r←'⎕SIGNAL 811'
              :EndIf
          :Case 803
              r←'⎕SIGNAL 812'
              ⎕←'Press Ctrl-Enter to invoke debugger'
          :Else
              :Trap 0
                  dmx←⎕DMX
                  ⎕←'*** MiServer Debug ***'
                  ⎕←'' 'occurred at:',⍪dmx.(EM(2⊃DM))
                  ⎕←'' 'SI Stack is ',(⍕¯1+⍴⎕XSI),' levels deep'
                  ⎕←''
              :EndTrap
              ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
              ⎕←'      ⎕SIGNAL 801 ⍝ to cut back and debug'
              r←''
          :EndSelect
      :EndIf
    ∇
    :endsection

:EndNamespace

:Namespace Boot

    (⎕ML ⎕IO)←1

    :section Startup/Shutdown

    ∇ Run root

      AppRoot←folderize root  ⍝ application (website) root
      AppRoot Load 1 ⍝ load essential objects
      #.Utils.SetupCompatibility
      ms←Init ConfigureServer AppRoot ⍝ read configuration and create server instance
      ConfigureDatasources ms
      ConfigureVirtual ms
      ConfigureResources ms
      ConfigureContentTypes ms
      ConfigureLogger ms

      ms.Run
    ∇

    ∇ {AppRoot}Load yes;files;f;classes;class;utils;t;core;HTML;extensions;HTMLsubdirs
      ⍝ Load required objects for MiServer
      ⍝ Note: DRC namespace is not SALTed
      ⍝ yes - 1 to perform load, 0 to clean up

      classes←''
      :If 0≠⎕NC'AppRoot'
          classes←(⎕SE.SALT.List AppRoot,'Code -raw')[;1 2] ⍝ Classes in application folder
          classes←(empty¨classes[;1])/classes[;2] ⍝ remove directory entries (have <DIR> in [;1])
      :EndIf
      utils←(⎕SE.SALT.List MSRoot,'Utils -raw')[;2]   ⍝ find utility libraries
      core←(⊂'Boot')~⍨(⎕SE.SALT.List MSRoot,'Core -raw')[;2]
      extensions←(⎕SE.SALT.List MSRoot,'Extensions -raw')[;2]
      HTML←(⎕SE.SALT.List MSRoot,'HTML -raw')[;2] ⍝ force to be loaded first
      HTMLsubdirs←HTML∩,⎕SE.SALT.List MSRoot,'HTML -folder'
      :If yes

          files←'Core/'∘,¨core
          files,←'Utils/'∘,¨utils
          files,←'Extensions/'∘,¨extensions

          :For f :In files
              disperror 1 ⎕SE.SALT.Load MSRoot,f,' -target=#' ⍝ do not reload already loaded spaces
          :EndFor

          #.SupportedHtml5Elements.Build_html_namespace

          :For f :In HTML
              disperror ⎕SE.SALT.Load MSRoot,'HTML/',f,' -target=#'
              :If (⊂f)∊HTMLsubdirs
                  disperror¨⎕SE.SALT.Load MSRoot,'HTML/',f,'/* -target=#.',f
              :EndIf
          :EndFor

⍝          :For f :In HTML∩,⎕SE.SALT.List MSRoot,'HTML -folder'
⍝              ⎕SE.SALT.Load MSRoot,'HTML/',f,'/* -target=#.',f
⍝          :EndFor

          :If 0≠⍴classes
              :For class :In classes
                  disperror ⎕SE.SALT.Load AppRoot,'Code/',class,' -target=#'
              :EndFor
          :EndIf

          'Pages'#.⎕NS'' ⍝ Container Space for loaded classes
          #.Pages.(MiPage MildPage RESTful)←#.(MiPage MildPage RESTful)
          BuildEAWC ⍝ build the Easy As ⎕WC namespace
          :If #.Files.DirExists AppRoot,'/Code/Templates/'
              disperror ⎕SE.SALT.Load AppRoot,'/Code/Templates/* -target=#.Pages'
          :EndIf

      :Else ⍝ Cleanup
          #.⎕EX¨classes
          #.⎕EX¨utils
          #.⎕EX'Pages'
          #.⎕EX¨'MiServer' 'HTTPRequest'
      :EndIf
    ∇

    ∇ LoadHTML f
    ⍝ Utility to reload an HTML content-generating namespace
      disperror ⎕SE.SALT.Load MSRoot,'HTML/',f,' -target=#'
      disperror¨⎕SE.SALT.Load MSRoot,'HTML/',f,'/* -target=#.',f
    ∇

    ∇ ms←Init Config;path;class;classes;e;res;mask
     ⍝ Create instances of MiServer, Session and Authentication Handlers

      ms←⎕NEW(#⍎Config.ClassName)Config
      path←MSRoot,'Extensions/'

      :If 0≠⍴Config.SessionHandler
          class←⎕SE.SALT.Load path,Config.SessionHandler
          ms.SessionHandler←⎕NEW class ms
      :EndIf

      :If 0≠⍴Config.Authentication
          class←⎕SE.SALT.Load path,Config.Authentication
          ms.Authentication←⎕NEW class ms
      :EndIf

      :If 0≠⍴Config.SupportedEncodings
          {}⎕SE.SALT.Load path,'ContentEncoder'
          :For e :In Config.SupportedEncodings
              class←⎕SE.SALT.Load path,e
              ms.Encoders,←⎕NEW class
          :EndFor
          :If ∨/mask←0≠1⊃¨res←ms.Encoders.Init
              2 ms.Log'Content Encoding Initialization failed for:',∊' ',¨mask/ms.Encoders.Encoding
              ms.Encoders←(~mask)/ms.Encoders
          :EndIf
      :EndIf
      Config.UseContentEncoding∧←0≠⍴ms.Encoders

      :If 0≠⍴Config.Logger
          class←⎕SE.SALT.Load path,Config.Logger
          ms.Logger←⎕NEW class ms
      :EndIf
    ∇

    ∇ End;classes;z;m
      ⍝ Clean up the workspace

      :If 9=⎕NC'ms'
          :Trap 0
              ms.End
          :EndTrap
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.SessionHandler'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Authentication'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Logger'
          {}try'⎕EX⍕¨∪enlist ⎕CLASS¨ms.Encoders'
          ⎕EX⍕⊃⊃⎕CLASS ms
          ⎕EX'ms'
      :EndIf

      :If 9=⎕NC'SQA'
          {}try'SQA.Close''.'''
      :EndIf

      :If 0≠⍴classes←↓#.⎕NL 9.4
      :AndIf 0≠⍴classes←(m←2=⊃∘⍴¨z←⎕CLASS¨#⍎¨classes)/classes
      :AndIf 0≠⎕NC'#.MiPage'
          classes←(#.MiPage≡¨2 1∘⊃¨m/z)/classes
          #.⎕EX↑⍕¨classes ⍝ Erase loaded classes
      :EndIf

      Load 0
      ⎕EX'#.MiPage'
      ⎕EX'AppRoot'
      {}try'#.DRC.Close ''.'''
    ∇

    ∇ BuildEAWC;src;sources;fields;source;list;mask;refs;target
     ⍝ Build the Easy As ⎕WC namespace from core classes and its own source
     ⍝ Also build the #._ namespace with shortcuts
      sources←#._html #._SF #._JQ #._DC #._JS
⍝      fields←''
      '_'#.⎕NS''
      :For source :In sources
          list←source.⎕NL ¯9.4
          list←list/⍨'_'≠⊃¨list
          :If ∨/mask←0≠refs←source{6::0 ⋄ (⍕⍺){('.'∊1↓s↓r)<⍺≡(s←⍴⍺)↑r←⍕⍵}t←⍺⍎⍵:t ⋄ 0}¨list
              #._⍎∊(mask/list){'⋄',⍺,'←',⍕⍵}¨mask/refs
          :EndIf
      :EndFor
    ∇

    :endsection

    :section Configuration

    ∇ r←ns Setting pars;name;num;default;mask
    ⍝ returns setting from a config style namespace or provides a default if it doesn't exist
    ⍝ pars - name [num] [default]
    ⍝ ns - namespace reference
    ⍝ name - name of the setting
    ⍝ num - 1 if setting is numeric scalar, (,1) if numeric vector is allowed, 0 otherwise
    ⍝ default - default value if not found
      pars←eis pars
      (name num)←2↑pars,(⍴pars)↓'' 0 ''
      :If 2<⍴pars ⋄ default←3⊃pars
      :Else ⋄ default←(1+num)⊃''⍬
      :EndIf
      r←(⍴ns)⍴⊂default
      :If ∨/mask←0≠⊃¨ns.⎕NC⊂name
          (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name
      :EndIf
      :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf
    ∇

    ∇ config←{element}ReadConfiguration type;serverconfig;file;siteconfig;thing;ind;mask
    ⍝ Attempt to read configuration file
    ⍝ 1) from server root MSRoot
    ⍝ 2) from site root AppRoot
    ⍝ merging the two if they both exist - site settings overrule server settings
      config←''
      :If #.Files.Exists file←MSRoot,'Config/',type,'.xml'
          config←serverconfig←(#.XML.ToNS #.Files.GetText file)⍎type
      :EndIf
      :If #.Files.Exists file←AppRoot,'Config/',type,'.xml'
          siteconfig←(#.XML.ToNS #.Files.GetText file)⍎type
          :If 0∊⍴config
              config←siteconfig
          :ElseIf 0=⎕NC'element'
              {}{try'serverconfig.',⍵,'←siteconfig.',⍵}¨siteconfig.⎕NL ¯2
          :Else ⍝ element specifies the element(s) to search on
              :For thing :In siteconfig
                  :If 0≠thing.⎕NC element
                      :If ∨/mask←0≠∊serverconfig.⎕NC⊂element
                          :If (+/mask)<ind←((mask/serverconfig)⍎¨⊂element)⍳⊂thing⍎element
                              serverconfig,←thing
                          :Else
                              serverconfig[(mask/⍳⍴mask)[ind]]←thing
                          :EndIf
                      :Else
                          serverconfig,←thing
                      :EndIf
                  :EndIf
              :EndFor
              config←serverconfig
          :EndIf
      :EndIf
    ∇

    ∇ Config←ConfigureServer AppRoot;file
    ⍝ configure server level settings, setting defaults for needed ones that are not supplied
      Config←ReadConfiguration'Server'

      Config.AllowedHttpCommands←{⎕ML←3 ⋄ ⍵⊂⍨~⍵∊' ,'}#.Strings.lc Config Setting'AllowedHttpCommands' 0 'get,post'
      Config.AppRoot←AppRoot
      Config.Authentication←Config Setting'Authentication' 0 'SimpleAuth'
      Config.CertFile←Config Setting'CertFile' 0 ''
      Config.ClassName←Config Setting'ClassName' 0 'MiServer'
      Config.Debug←Config Setting'Debug' 1 0
      Config.DefaultExtension←Config Setting'DefaultExtension' 0 '.mipage'
      Config.DefaultPage←Config Setting'DefaultPage' 0 'index.mipage'
      Config.FormatHtml←Config Setting'FormatHtml' 1 0
      Config.Host←Config Setting'Host' 0 'localhost'
      Config.HttpCacheTime←Config Setting'HttpCacheTime' 1 0 ⍝ default to off (0)
      Config.IPVersion←Config Setting'IPVersion' 0 'IPV4'
      Config.IdleTimeOut←Config Setting'IdleTimeOut' 1 0 ⍝ default to none (0)
      Config.KeyFile←Config Setting'KeyFile' 0 ''
      Config.Lang←Config Setting'Lang' 0 'en'
      Config.LogMessageLevel←Config Setting'LogMessageLevel' 1 1 ⍝ default to error messages only
      Config.Logger←Config Setting'Logger' 0 ''
      Config.MSRoot←MSRoot
      Config.Name←Config Setting'Name' 0 'MiServer'
      Config.Port←Config Setting'Port' 1 8080
      Config.Ports←Config Setting'Ports'(,1)⍬
      Config.Production←Config Setting'Production' 1 0 ⍝ production mode?  (0/1 = development debug framework en/disabled)
      Config.RESTful←Config Setting'RESTful' 1 0 ⍝ RESTful web service?
      Config.RootCertDir←Config Setting'RootCertDir' 0 ''
      Config.Root←AppRoot ⍝ folderize MSRoot{((isRelPath ⍵)/⍺),⍵}AppRoot
      Config.SSLFlags←Config Setting'SSLFlags' 1(32+64)  ⍝ Accept Without Validating, RequestClientCertificate
      Config.Secure←Config Setting'Secure' 1 0
      Config.Server←Config Setting'Server' 0 ''
      Config.SessionHandler←Config Setting'SessionHandler' 0 'SimpleSessions'
      Config.SessionTimeOut←Config Setting'SessionTimeOut' 1 30 ⍝ 30 minute timeout
      Config.SupportedEncodings←{(⊂'')~⍨1↓¨(⍵=⊃⍵)⊂⍵}',',Config Setting'SupportedEncodings' 0
      Config.TempFolder←folderize Config.Root{0∊⍴⍵:⍵ ⋄ ((isRelPath ⍵)/⍺),⍵}Config Setting'TempFolder' 0
      Config.TrapErrors←Config Setting'TrapErrors' 1 0
      Config.UseContentEncoding←Config Setting'UseContentEncoding' 1 0 ⍝ aka HTTP Compression default off (0)

      :If 0≠⎕NC'#.DrA' ⍝ Transfer DrA config options
          {}#.DrA.SetDefaults
          #.DrA.AppName←Config.Name
          #.DrA.MailMethod←Config Setting'MailMethod' 0 'NONE'
          #.DrA.MailRecipient←Config Setting'MailRecipient' 0
          #.DrA.Mode←Config.Debug
          #.DrA.NoUser←Config Setting'NoUser' 1 1 ⍝ run without user interaction
          #.DrA.Path←AppRoot ⍝ Where to put log files
          #.DrA.SMTP_Gateway←Config Setting'SMTP_Gateway' 0
          #.DrA.UseHTTP←Config Setting'UseHTTP' 1 0
      :EndIf
    ∇

    ∇ ConfigureDatasources ms;file;ds;name;tmp;orig;dyalog
      ⍝ load any datasource definitions
      :If ~0∊⍴ms.Datasources←'Name'ReadConfiguration'Datasources'
          :For ds :In ms.Datasources
              :For name :In ds.⎕NL ¯2
                  orig←tmp←ds.⍎name
                  tmp←SubstPath tmp
                  :If orig≢tmp
                      ⍎'ds.',name,'←tmp'
                  :EndIf
              :EndFor
          :EndFor

          :Trap 0
              :If 0=#.⎕NC'SQA'
                  dyalog←2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'
                  'SQA'#.⎕CY dyalog,'\ws\sqapl' ⍝ copy in SQA
              :EndIf
              :If 0≠1⊃#.SQA.Init'' ⍝ and initialize
                  1 ms.Log'SQA failed to initialize'
              :EndIf
          :Else
              1 ms.Log'SQA failed to initialize'
          :EndTrap
      :EndIf
    ∇

    ∇ ConfigureVirtual ms;file;virtual;mask;inds;v
      ⍝ load virtual (alias) definitions if any
      ms.Config.Virtual←''
      :If ~0∊⍴virtual←'alias'ReadConfiguration'Virtual'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}virtual.alias ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate virtual aliases defined (first occurrence will be used): ',1↓enlist',',¨(~mask)/virtual.alias
              virtual←mask/virtual
          :EndIf
          ⍝ substitute server and site root if found
          virtual.path←SubstPath¨virtual.path
          :For v :In virtual
              :If ~#.Files.DirExists v.path ⍝ check if mapped path exists
                  1 ms.Log'Virtual path not found: ',v.path
                  virtual~←v
              :Else
                  :If #.Files.DirExists AppRoot,v.alias ⍝ check if alias conflicts with local path
                      1 ms.Log'Virtual alias "',v.alias,'" overrides site path of same name.'
                  :EndIf
              :EndIf
          :EndFor
          ms.Config.Virtual←virtual
      :EndIf
    ∇

    ∇ ConfigureResources ms;file;mask;inds;names;uses;map;n;res;f;missing;order
      ⍝ load resource definitions if any
      ms.Config.Resources←0 3⍴⊂''
      :If ~0∊⍴res←'name'ReadConfiguration'Resources'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}names←res.name ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate resources defined (first occurence will be used): ',1↓enlist',',¨(~mask)/res.name
              res←mask/res
          :EndIf
          ⍝ build the dependency map
          uses←{(eis⍣(⊃0<⍴⍵))⍵}¨res Setting'uses'
          inds←res.name∘⍳¨uses
          n←⊃⍴res
          :If ∨/mask←∨/¨missing←n<inds
              1 ms.Log'Invalid resource dependency found for:',enlist' ',¨mask/res.name
              inds←(~missing)/¨inds
          :EndIf
          order←OrderResources inds
          inds←(order∘⍳¨inds)[order]
          res←res[order]
          map←,(2⍴n)⍴(1+n)↑1 ⍝ identity matrix
          map[enlist(n×¯1+⍳n)+¨inds]←1
          map←(2⍴n)⍴map
          map←↓{({⍺∨⍺∨.∧⍵}⍣≡)⍨⍵}map
          ⍝ ms.Config.Resources[;1] resource name, [;2] scripts used [;3] styles used
          f←{SubstPath¨{⍵~⊂''}∘∪¨⊃¨,/¨map/¨⊂eis¨⍵}
          ms.Config.Resources←res.name,(f res Setting'script'),[1.1]f res Setting'style'
      :EndIf
    ∇

    ∇ r←OrderResources inds;n;mask
      n←⍳⍴inds
      r←n/⍨mask←0∘∊∘⍴¨inds ⍝ roots with no dependencies
      inds/⍨←~mask
      n/⍨←~mask
      :While ~0∊⍴inds
          mask←∧/¨inds∊¨⊂r
          r,←mask/n
          inds/⍨←~mask
          n/⍨←~mask
      :EndWhile
    ∇

    ∇ ConfigureContentTypes ms;file;ct;inds;exts;types;mask;exp;n
      ⍝ load supported content types
      ms.Config.ContentTypes←0 2⍴⊂''
      :If ~0∊⍴ct←ReadConfiguration'ContentTypes'
          exts←#.Strings.lc¨ct Setting'ext'
          types←ct Setting'type'
          :If ~0∊⍴inds←{⍵/⍳⍴⍵}enlist','∊¨exts
              mask←(⍴exts)⍴1
              exp←{⎕ML←3 ⋄ (⍵≠',')⊂⍵}¨exts[inds]
              mask[inds]←n←⍬∘⍴∘⍴¨exp
              (exts types)←mask∘/¨exts types
              exts[(n/inds)++\~enlist n↑¨1]←⊃,/exp
          :EndIf
          ms.Config.ContentTypes←exts,[1.1]types
      :Else
          1 ms.Log'No content types defined?'
      :EndIf
    ∇

    ∇ ConfigureLogger ms;file;log
      ⍝ load logger information
      ms.Config.Logger←⎕NS''
      ms.Config.Logger.active←0
      :If ~0∊⍴log←ReadConfiguration'Logger'
          ms.Config.Logger.active←log Setting'active' 1 0
          ms.Config.Logger.directory←SubstPath log Setting'directory' 0 ''
          ms.Config.Logger.frequency←log Setting'frequency' 0 ''
          ms.Config.Logger.sizelimit←log Setting'sizelimit' 1 0
      :EndIf
    ∇

    :endsection

    :section Utilities
    disperror←{}∘{326=⎕DR ⍵:'' ⋄ '***'≡3↑⍵:⎕←⍵ ⋄ ''}
    isWin←('.' ⎕WG 'APLVersion')[3]≡⊂,'W'
    fileSep←'/\'[1+isWin]
    MSRoot←{(1-⌊/'/\'⍳⍨⌽⍵)↓⍵}⎕WSID
    isRelPath←{{~'/\'∊⍨(⎕IO+2×isWin∧':'∊⍵)⊃⍵}3↑⍵}
    enlist←{⎕ML←1⋄∊⍵}
      tonum←{⍺←0
          1∊⍺:tonumvec ⍵
          w←⍵ ⋄ ((w='-')/w)←'¯'
          ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w}
    try←{0::'' ⋄⍎⍵}
    empty←{0∊⍴⍵}
    notEmpty←~∘empty
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    isRef←{(0∊⍴⍴⍵)∧326=⎕DR ⍵}
    folderize←{¯1↑⍵∊'/\':⍵ ⋄ ⍵,fileSep}
    makeSitePath←{⍺{((isRelPath ⍵)/⍺),⍵},folderize ⍵}

    ∇ r←tonumvec v;to;minus;digits;c;mask
    ⍝ tonum vector version
    ⍝ allows for specific of ranges and comma or space delimited numbers
    ⍝ tonumvec '8080-8090'  or '5,7-9,11-15'
      r←⍬
      ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯'
      to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵}
      v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v
      minus←'-'=v
      digits←v∊⎕D,'.'
      ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯'
      ((' '=v)/v)←','
      (('-'=v)/v)←⊂' to '
      :Trap 0
          :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v
              r,←⍎∊c
          :EndFor
      :Else
          ⎕SIGNAL 11
      :EndTrap
    ∇

    ∇ r←SubstPath r
      r←(#.Strings.subst∘('%ServerRoot%'(¯1↓MSRoot)))r
      r←(#.Strings.subst∘('%SiteRoot%'(¯1↓AppRoot)))r
    ∇

    ∇ r←isRunning
      :Trap r←0
          r←ms.TID∊⎕TNUMS
      :EndTrap
    ∇

    ∇ r←Oops;dmx;ends;xsi
    ⍝ debugging framework to bubble up to user's code when rendering fails
      r←'⎕SIGNAL 811'
      ends←{(,⍺)≡(-⍴,⍺)↑⍵}
      :If {0::0 ⋄ #.HtmlPage∊∊⎕CLASS ⍵}⊃⊃⎕RSI
          r←'⎕TRAP←(800 ''C'' ''→FAIL'')(811 ''E'' ''⎕SIGNAL 801'')(813 ''E'' ''⎕SIGNAL 803'')(812 ''S'')(85 ''N'')(0 ''S'')'
          ⎕←''
          ⎕←'*** MiServer Debug ***'
          ⎕←↑⎕DMX.DM
          ⎕←''
          ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
          ⎕←'      or Press Ctrl-Enter to invoke debugger'
      :Else
          :Select ⎕DMX.EN
          :Case 801
              xsi←⎕XSI
              :If '.HandleMSP'ends 2⊃xsi
                  r←'→FAIL'
              :ElseIf '.HandleMSP'ends 4⊃xsi
                  :If '.Render'ends 3⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :ElseIf '.HandleMSP'ends 3⊃xsi
                  :If '.Wrap'ends 2⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :Else
                  r←'⎕SIGNAL 811'
              :EndIf
          :Case 803
              r←'⎕SIGNAL 812'
              ⎕←'Press Ctrl-Enter to invoke debugger'
          :Else
              :Trap 0
                  dmx←⎕DMX
                  ⎕←'*** MiServer Debug ***'
                  ⎕←'' 'occurred at:',⍪dmx.(EM(2⊃DM))
                  ⎕←'' 'SI Stack is ',(⍕¯1+⍴⎕XSI),' levels deep'
                  ⎕←''
              :EndTrap
              ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
              ⎕←'      ⎕SIGNAL 801 ⍝ to cut back and debug'
              r←''
          :EndSelect
      :EndIf
    ∇
    :endsection

:EndNamespace


Remove injection of hostname into URLs

Remove injection of hostname into URLs

:Class MiPage : #.HtmlPage

    ⍝∇:require =\HtmlPage.dyalog
    ⍝∇:require =\JSON.dyalog

    :Field Public _PageName←'' ⍝ Page file name
    :Field Public _PageDate←'' ⍝ Page saved date
    :field Public _Request     ⍝ HTTPRequest
    :field Public _Scripts←''
    :field Public _Styles←''
    :field Public _CssReset←''    ⍝ location of CSS Reset file (if any)
    :field Public _CssOverride←'' ⍝ location of CSS Override file (if any)
    :field Public _Serialized←1   ⍝ serialized forms to return in _PageData
    :field Public _event          ⍝ set by APLJAX callback - event that was triggered
    :field Public _what           ⍝ set by APLJAX callback - name or id of the triggering element
    :field Public _value          ⍝ set by APLJAX callback - value of the triggering element
    :field Public _selector       ⍝ set by APLJAX callback - CSS/jQuery selector for the element that triggered the event
    :field Public _callback       ⍝ set by APLJAX callback - name of the callback function
    :field Public _target         ⍝ set by APLJAX callback - id of the target element
    :field Public _currentTarget  ⍝ set by APLJAX callback - id of the currentTarget element
    :field Public _PageData       ⍝ namespace containing any data passed via forms or URL
    :field Public _AjaxResponse←''
    :field Public _DebugCallbacks←0
    :field Public _TimedOut←0
    :field Public _cache←''       ⍝ cached content if page is marked cacheable
    :field Public OnLoad←''       ⍝ page equivalent to ⎕LX
    :field Public Cacheable←0     ⍝ is the page cacheable?
    :field Public Charset←'UTF-8' ⍝ charset for page

    _used←'' ⍝ track what's been Use'd

    ∇ Make
      :Access public
      :Implements constructor :Base
      MakeCommon
    ∇

    ∇ Make1 req
      :Access public
      _Request←req
      :Implements constructor :base
      MakeCommon
    ∇

    ∇ MakeCommon
      _PageData←⎕NS''
    ∇

    ∇ {r}←Render;b;styles;_head;_body;_styles;_scripts;t;i
      :Access public
   ⍝  Capture the current Head and Body content so that we can reset it after rendering
   ⍝  This is so we can re-render and still get the same result

      (_head _body)←(Head Body).Content
      (_styles _scripts)←_Styles _Scripts

      :If ''≢OnLoad
          {0:: ⋄ Use'JQuery'}''
      :EndIf

      b←RenderBody

      :If ~0∊⍴_Scripts
          {(Insert _html.script).Set('src'⍵)}¨⌽∪_Scripts
      :EndIf

      styles←∪_Styles
      styles←styles,⍨{0∊⍴⍵:⍵ ⋄ ⊂⍵}_CssReset
      styles←styles,{0∊⍴⍵:⍵ ⋄ ⊂⍵}_CssOverride

      :If ~0∊⍴styles
          {Insert #._DC.StyleSheet ⍵}¨⌽∪styles
      :EndIf

      t←''
      :If ~0∊⍴Handlers
          t←,∊Handlers.Render
      :EndIf
      :If ~0∊⍴OnLoad
          t,←(⎕NEW #._html.script('$(function(){',OnLoad,'});')).Render
      :EndIf
      :If ~0∊⍴t
          :If '</body>'≡¯7↑b
              i←¯7
          :Else
              i←-6+⊃⍸'>ydob/<'⍷⌽b
          :EndIf
          b←(i↓b),t,i↑b
      :EndIf

      :If ''≢OnLoad

      :EndIf

      :If 0∊⍴⊃Attrs[⊂'lang'] ⍝ set the language for the page if not already set
          {0:: ⋄ {Set'lang="',⍵,'" xml:lang="',⍵,'" xmlns="http://www.w3.org/1999/xhtml"'}_Request.Server.Config.Lang}''
      :EndIf

      :If ~0∊⍴Charset
          Insert _html.meta''('charset="',Charset,'"')
      :EndIf

      r←RenderPage b

      :If 0≠⎕NC⊂'_Request.Response'
          _Request.Response.HTML←r
      :EndIf

      (Head Body).Content←_head _body
      _Styles←_styles
      _Scripts←_scripts
      _used←''
    ∇

    ∇ {r}←Wrap
      :Access public
      r←Render
      :If Cacheable ⋄ _cache←r ⋄ :EndIf
    ∇

    ∇ Use resources;n;ind;t;x;server
      :Access public
      resources←eis resources
      :For x :In resources
          :If ~(⊂x)∊_used
              :Select ⊃x
              :Case '⍎' ⍝ script
                  _Scripts,←⊂1↓x
              :Case '⍕' ⍝ style sheet
                  _Styles,←⊂1↓x
              :Else
                  :If 0≠⎕NC'_Request.Server'
                      server←_Request.Server
                  :ElseIf 0≠⎕NC'#.Boot.ms'
                      server←#.Boot.ms
                  :EndIf
                  :If 0≠server.⎕NC⊂'Config.Resources'
                  :AndIf ~0∊n←1↑⍴server.Config.Resources
                      :If n≥ind←server.Config.Resources[;1]⍳⊂x
                          :If ~0∊⍴t←{(~0∘∊∘⍴¨⍵)/⍵}(⊂ind 2)⊃server.Config.Resources
                              _Scripts,⍨←t
                          :EndIf
                          :If ~0∊⍴t←{(~0∘∊∘⍴¨⍵)/⍵}(⊂ind 3)⊃server.Config.Resources
                              _Styles,⍨←t
                          :EndIf
                      :Else
                          1 server.Log _PageName,' references unknown resource: ',x
                      :EndIf
                  :EndIf
              :EndSelect
              _used,←⊂x
          :EndIf
      :EndFor
    ∇

    ∇ r←{proto}Get names;noproto;char
      :Access public
      :If noproto←0=⎕NC'proto' ⋄ proto←'' ⋄ :EndIf
      names←eis names
      names←,⍕names
      names←#.Strings.deb names
      :If ' '∊names
          names←{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          r←({⍬∘⍴⍣(1=≢⍵)⊢⍵}eis proto)Get¨names
      :ElseIf ~(_PageData.⎕NC names)∊2 9
          r←,proto
      :Else
          r←_PageData⍎names
          :If 2≤≡r
              :If 1=⍴,r
                  r←⊃r
              :EndIf
          :EndIf
          :If noproto≥char←0=2|⎕DR proto
          :AndIf isString r
              r←#.JSON.toAPL r
          :EndIf
          :If noproto⍱char
              :If 1≠2|⎕DR r
                  r←,proto
              :EndIf
          :EndIf
      :EndIf
    ∇

    ∇ r←GetNames str
      :Access public
      →0⍴⍨0∊⍴r←_PageData.⎕NL-2 9
      →0⍴⍨0∊⍴str
      r←r/⍨r #.Strings.beginsWith¨⊂str
    ∇

    ∇ r←{proto}GetRaw names
      :Access public
      proto←{6::⍵ ⋄ proto}''
      names←eis names
      names←,⍕names
      names←#.Strings.deb names
      :If ' '∊names
          names←{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          r←proto∘Get¨names
      :ElseIf 2≠_PageData.⎕NC names
          r←,proto
      :Else
          r←_PageData⍎names
          :If 2=≡r
              :If 1=⍴,r
                  r←⊃r
              :EndIf
          :EndIf
          :If ~isChar proto
              r←{0::⍵ ⋄ 0∊⍴⍵:⍬ ⋄ w←⍵ ⋄ ((w='-')/w)←'¯' ⋄ ⊃(//)⎕VFI w}r
          :EndIf
      :EndIf
    ∇

    ∇ r←{proto}SessionGet names
      :Access public
      proto←{6::⍵ ⋄ proto}''
      names←,⍕names
      names←#.Strings.deb names
      :If ' '∊names
          names←{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          r←proto∘SessionGet¨names
      :ElseIf 0=_Request.Session.⎕NC names
          r←proto
      :Else
          r←_Request.Session⍎names
          :If 1<|≡r ⋄ r←∊r ⋄ :EndIf
          :If ~0 2∊⍨10|⎕DR proto  ⍝ if the prototype is numeric
          :AndIf 0 2∊⍨10|⎕DR r    ⍝ and the element is character
              r←{0∊⍴⍵:⍬ ⋄ w←⍵ ⋄ ((w='-')/w)←'¯' ⋄ ⊃(//)⎕VFI w}r
          :EndIf
      :EndIf
    ∇

    ∇ _Close session ⍝ Called when the session ends
    ⍝ this method is specific to #.SimpleSessions and is called when a session is terminated
      :Access Public Overridable
    ∇

    :section APLJax  ⍝ used for building APLJAX responses
    ∇ _resetAjax
      :Access public
      _AjaxResponse←''
    ∇

    ∇ r←_id
      :Access public
      ⍝ as there seems to be a problem with at least some Syncfusion widgets
      ⍝ being able to provide the id (or name) of the triggering element, we try to obtain it from _what or _selector
      r←''
      :Trap 0
          :If 0∊⍴r←_what
          :AndIf '#'=⊃_selector
              r←1↓_selector
          :EndIf
      :EndTrap
    ∇

    ∇ r←renderContent content;c
      :Access public shared
      r←''
      content←eis content
      :Trap 0
          :While ~0∊⍴content
              :Select ≡c←⊃content
              :Case 0
                  :If isClass c
                      :Select ⊃⍴content
                      :Case 1
                          r,←(⎕NEW c).Render
                      :Case 2
                          r,←(⎕NEW c(2⊃content)).Render
                      :Else
                          r,←(⎕NEW c(1↓content)).Render
                      :EndSelect
                  :ElseIf isInstance c
                      r,←c.Render
                  :Else
                      r,←(⎕NEW #.HtmlElement(''content)).Render
                  :EndIf
                  content←''
              :Case 1
                  :If isClass⊃c
                      r,←(⎕NEW(⊃c)(1↓c)).Render
                  :ElseIf isInstance⊃c
                      ∘∘∘ ⍝ should not happen! (I think)
                  :Else
                      r,←(⎕NEW #.HtmlElement(''c)).Render
                  :EndIf
                  content←1↓content
              :Else
                  r,←renderContent c
                  content←1↓content
              :EndSelect
          :EndWhile
      :Else
          :If (⎕EN≥100)∧(⎕EN≤999) ⍝ HTTP status rather than an APL error
          :AndIf 0≠⎕NC⊂'_Request.Response' ⍝ we're actually running a server, not rendering in session
              _Request.Fail ⎕EN
          :Else ⍝ APL errors and in-session errors are not trapped:
              ⎕SIGNAL ⎕DMX.(⊂{⍵(⍎⍵)}¨(⎕NL-⍳9)~'InternalLocation' 'DM' 'OSError') ⍝ re-signal
          :EndIf
      :EndTrap
    ∇

    ∇ r←selector Replace content
      :Access public shared
      r←⊂('replace'selector)('data'(renderContent content))
    ∇
    ∇ r←selector Append content
      :Access public shared
      r←⊂('append'selector)('data'(renderContent content))
    ∇
    ∇ r←selector Prepend content
      :Access public shared
      r←⊂('prepend'selector)('data'(renderContent content))
    ∇
    ∇ r←Execute content
      :Access public shared
      r←⊂('execute'(renderContent content))
    ∇

    ∇ r←name Assign data
      :Access public shared
      r←⊂('assign'name)('data'data)
    ∇

    :endsection

    :section Position

    ∇ ref Position args;inds;mask;parameters;my;at;of;collision;within;q
      :Access public
      ⍝ ref  - a reference to an instance of anything based on HtmlElement
      ⍝ args - position information per jQueryUI's Position widget http://api.jqueryui.com/position/
      ⍝        can be in any of the following forms
      ⍝      1) positional (my at of collision within)  N.B. we don't use the "using" parameter
      ⍝         example:  myDiv Position 'left top' 'right bottom' '#otherElement'
      ⍝                   positions myDiv's top left corner at the bottom right corner of the element with id "otherElement"
      ⍝      2) paired
      ⍝                   myDiv Position 'my' 'left top' 'at' 'right bottom' 'of' '#otherElement'
      ⍝                   myDiv Position ('my' 'left top') ('at' 'right bottom') ('of' '#otherElement')
      ⍝                   myDiv Position 3 2⍴'my' 'left top' 'at' 'right bottom' 'of' '#otherElement'
      ⍝ Note: positional arguments are in form horizontal (left center right) vertical (top center bottom)

      parameters←'my' 'at' 'of' 'collision' 'within'
      q←{1⌽'''''',{⍵/⍨1+''''=⍵}⍕⍵}
      :If isInstance ref
          :If 2=⍴⍴args ⍝ matrix
              args←,args
          :ElseIf 3=≡args
              args←⊃,/args
          :EndIf
          args←eis args
          inds←parameters⍳args
          :If ∨/mask←inds≤⍴parameters
              :If mask≡(2×+/mask)⍴1 0
                  parameters←mask/args
                  args←(1⌽mask)/args
              :EndIf
          :Else
              parameters←(⍴args)↑parameters
          :EndIf
          mask←⍬∘≢¨args
          args←mask/args
          parameters←mask/parameters
          parameters(ref{⍺⍺⍎'Position.',⍺,'←',q ⍵})¨args
          ref.Uses,←⊂'JQueryUI'
          SetUse
      :EndIf
    ∇
    :endsection

    :section Event Handling Support
    ∇ r←isPost
      :Access public
      r←{0::0 ⋄ _Request.isPost}⍬
    ∇

    ∇ r←isAPLJax
      :Access public
      r←{0::0 ⋄ _Request.isAPLJAX}⍬
    ∇

    ∇ r←sel Css args ⍝ JQuery css cover
      :Access public
      r←(sel #._JSS.JQuery'css')args
    ∇

    ∇ r←sel Val args ⍝ JQuery val cover
      :Access public
      r←(sel #._JSS.JQuery'val')args
    ∇

    ∇ r←sel Prop args ⍝ JQuery prop cover
      :Access public
      r←(sel #._JSS.JQuery'prop')args
    ∇

    ∇ r←sel Attr args ⍝ JQuery attr cover
      :Access public
      r←(sel #._JSS.JQuery'attr')args
    ∇

    ∇ r←sel RemoveAttr args ⍝ JQuery removeAttr cover
      :Access public
      r←(sel #._JSS.JQuery'removeAttr')args
    ∇

    ∇ r←sel Html args ⍝ JQuery html cover
      :Access public
      r←(sel #._JSS.JQuery'html')args
    ∇

    ∇ r←sel Show args ⍝ JQuery show cover
      :Access public
      r←(sel #._JSS.JQuery'show')args
    ∇

    ∇ r←sel Hide args ⍝ JQuery hide cover
      :Access public
      r←(sel #._JSS.JQuery'hide')args
    ∇

    ∇ r←sel Toggle args ⍝ JQuery toggle cover
      :Access public
      r←(sel #._JSS.JQuery'toggle')args
    ∇

    ∇ r←Submit sel ⍝ JQuery submit cover
      :Access public
      r←(sel #._JSS.JQuery'submit')''
    ∇

    :endsection

:EndClass

:Class MiPage : #.HtmlPage

    ⍝∇:require =\HtmlPage.dyalog
    ⍝∇:require =\JSON.dyalog

    :Field Public _PageName←'' ⍝ Page file name
    :Field Public _PageDate←'' ⍝ Page saved date
    :field Public _Request     ⍝ HTTPRequest
    :field Public _Scripts←''
    :field Public _Styles←''
    :field Public _CssReset←''    ⍝ location of CSS Reset file (if any)
    :field Public _CssOverride←'' ⍝ location of CSS Override file (if any)
    :field Public _Serialized←1   ⍝ serialized forms to return in _PageData
    :field Public _event          ⍝ set by APLJAX callback - event that was triggered
    :field Public _what           ⍝ set by APLJAX callback - name or id of the triggering element
    :field Public _value          ⍝ set by APLJAX callback - value of the triggering element
    :field Public _selector       ⍝ set by APLJAX callback - CSS/jQuery selector for the element that triggered the event
    :field Public _callback       ⍝ set by APLJAX callback - name of the callback function
    :field Public _target         ⍝ set by APLJAX callback - id of the target element
    :field Public _currentTarget  ⍝ set by APLJAX callback - id of the currentTarget element
    :field Public _PageData       ⍝ namespace containing any data passed via forms or URL
    :field Public _AjaxResponse←''
    :field Public _DebugCallbacks←0
    :field Public _TimedOut←0
    :field Public _cache←''       ⍝ cached content if page is marked cacheable
    :field Public OnLoad←''       ⍝ page equivalent to ⎕LX
    :field Public Cacheable←0     ⍝ is the page cacheable?
    :field Public Charset←'UTF-8' ⍝ charset for page

    _used←'' ⍝ track what's been Use'd

    ∇ Make
      :Access public
      :Implements constructor :Base
      MakeCommon
    ∇

    ∇ Make1 req
      :Access public
      _Request←req
      :Implements constructor :base
      MakeCommon
    ∇

    ∇ MakeCommon
      _PageData←⎕NS''
    ∇

    ∇ {r}←Render;b;styles;_head;_body;_styles;_scripts;host;t;i
      :Access public
   ⍝  Capture the current Head and Body content so that we can reset it after rendering
   ⍝  This is so we can re-render and still get the same result

      (_head _body)←(Head Body).Content
      (_styles _scripts)←_Styles _Scripts
      host←{6::⍵ ⋄ '//',_Request.Host}''

      :If ''≢OnLoad
          {0:: ⋄ Use'JQuery'}''
      :EndIf

      b←RenderBody

      :If ~0∊⍴_Scripts
          {(Insert _html.script).Set('src'⍵)}¨host∘AddHost¨⌽∪_Scripts
      :EndIf

      styles←∪_Styles
      styles←styles,⍨{0∊⍴⍵:⍵ ⋄ ⊂⍵}_CssReset
      styles←styles,{0∊⍴⍵:⍵ ⋄ ⊂⍵}_CssOverride

      :If ~0∊⍴styles
          {Insert #._DC.StyleSheet ⍵}¨host∘AddHost¨⌽∪styles
      :EndIf

      t←''
      :If ~0∊⍴Handlers
          t←,∊Handlers.Render
      :EndIf
      :If ~0∊⍴OnLoad
          t,←(⎕NEW #._html.script('$(function(){',OnLoad,'});')).Render
      :EndIf
      :If ~0∊⍴t
          :If '</body>'≡¯7↑b
              i←¯7
          :Else
              i←-6+⊃⍸'>ydob/<'⍷⌽b
          :EndIf
          b←(i↓b),t,i↑b
      :EndIf

      :If ''≢OnLoad

      :EndIf

      :If 0∊⍴⊃Attrs[⊂'lang'] ⍝ set the language for the page if not already set
          {0:: ⋄ {Set'lang="',⍵,'" xml:lang="',⍵,'" xmlns="http://www.w3.org/1999/xhtml"'}_Request.Server.Config.Lang}''
      :EndIf

      :If ~0∊⍴Charset
          Insert _html.meta''('charset="',Charset,'"')
      :EndIf

      r←RenderPage b

      :If 0≠⎕NC⊂'_Request.Response'
          _Request.Response.HTML←r
      :EndIf

      (Head Body).Content←_head _body
      _Styles←_styles
      _Scripts←_scripts
      _used←''
    ∇

    ∇ {r}←Wrap
      :Access public
      r←Render
      :If Cacheable ⋄ _cache←r ⋄ :EndIf
    ∇

    ∇ Use resources;n;ind;t;x;server
      :Access public
      resources←eis resources
      :For x :In resources
          :If ~(⊂x)∊_used
              :Select ⊃x
              :Case '⍎' ⍝ script
                  _Scripts,←⊂1↓x
              :Case '⍕' ⍝ style sheet
                  _Styles,←⊂1↓x
              :Else
                  :If 0≠⎕NC'_Request.Server'
                      server←_Request.Server
                  :ElseIf 0≠⎕NC'#.Boot.ms'
                      server←#.Boot.ms
                  :EndIf
                  :If 0≠server.⎕NC⊂'Config.Resources'
                  :AndIf ~0∊n←1↑⍴server.Config.Resources
                      :If n≥ind←server.Config.Resources[;1]⍳⊂x
                          :If ~0∊⍴t←{(~0∘∊∘⍴¨⍵)/⍵}(⊂ind 2)⊃server.Config.Resources
                              _Scripts,⍨←t
                          :EndIf
                          :If ~0∊⍴t←{(~0∘∊∘⍴¨⍵)/⍵}(⊂ind 3)⊃server.Config.Resources
                              _Styles,⍨←t
                          :EndIf
                      :Else
                          1 server.Log _PageName,' references unknown resource: ',x
                      :EndIf
                  :EndIf
              :EndSelect
              _used,←⊂x
          :EndIf
      :EndFor
    ∇

    ∇ r←{proto}Get names;noproto;char
      :Access public
      :If noproto←0=⎕NC'proto' ⋄ proto←'' ⋄ :EndIf
      names←eis names
      names←,⍕names
      names←#.Strings.deb names
      :If ' '∊names
          names←{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          r←({⍬∘⍴⍣(1=≢⍵)⊢⍵}eis proto)Get¨names
      :ElseIf ~(_PageData.⎕NC names)∊2 9
          r←,proto
      :Else
          r←_PageData⍎names
          :If 2≤≡r
              :If 1=⍴,r
                  r←⊃r
              :EndIf
          :EndIf
          :If noproto≥char←0=2|⎕DR proto
          :AndIf isString r
              r←#.JSON.toAPL r
          :EndIf
          :If noproto⍱char
              :If 1≠2|⎕DR r
                  r←,proto
              :EndIf
          :EndIf
      :EndIf
    ∇

    ∇ r←GetNames str
      :Access public
      →0⍴⍨0∊⍴r←_PageData.⎕NL-2 9
      →0⍴⍨0∊⍴str
      r←r/⍨r #.Strings.beginsWith¨⊂str
    ∇

    ∇ r←{proto}GetRaw names
      :Access public
      proto←{6::⍵ ⋄ proto}''
      names←eis names
      names←,⍕names
      names←#.Strings.deb names
      :If ' '∊names
          names←{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          r←proto∘Get¨names
      :ElseIf 2≠_PageData.⎕NC names
          r←,proto
      :Else
          r←_PageData⍎names
          :If 2=≡r
              :If 1=⍴,r
                  r←⊃r
              :EndIf
          :EndIf
          :If ~isChar proto
              r←{0::⍵ ⋄ 0∊⍴⍵:⍬ ⋄ w←⍵ ⋄ ((w='-')/w)←'¯' ⋄ ⊃(//)⎕VFI w}r
          :EndIf
      :EndIf
    ∇

    ∇ r←{proto}SessionGet names
      :Access public
      proto←{6::⍵ ⋄ proto}''
      names←,⍕names
      names←#.Strings.deb names
      :If ' '∊names
          names←{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          r←proto∘SessionGet¨names
      :ElseIf 0=_Request.Session.⎕NC names
          r←proto
      :Else
          r←_Request.Session⍎names
          :If 1<|≡r ⋄ r←∊r ⋄ :EndIf
          :If ~0 2∊⍨10|⎕DR proto  ⍝ if the prototype is numeric
          :AndIf 0 2∊⍨10|⎕DR r    ⍝ and the element is character
              r←{0∊⍴⍵:⍬ ⋄ w←⍵ ⋄ ((w='-')/w)←'¯' ⋄ ⊃(//)⎕VFI w}r
          :EndIf
      :EndIf
    ∇

    ∇ url←{host}AddHost url
      ⍝ adds the request host name to the URL if it's not already there
      :Access public shared
      :If 0=⎕NC'host' ⋄ host←{6::⍵ ⋄ _Request.Host}'' ⋄ :EndIf
      :If ''≢host
      :AndIf ~∨/((⊃⍷#.Strings.nocase)∘url)¨'//' 'http:' 'https:'
          url,⍨←host
      :EndIf
    ∇

    ∇ _Close session ⍝ Called when the session ends
    ⍝ this method is specific to #.SimpleSessions and is called when a session is terminated
      :Access Public Overridable
    ∇

    :section APLJax  ⍝ used for building APLJAX responses
    ∇ _resetAjax
      :Access public
      _AjaxResponse←''
    ∇

    ∇ r←_id
      :Access public
      ⍝ as there seems to be a problem with at least some Syncfusion widgets
      ⍝ being able to provide the id (or name) of the triggering element, we try to obtain it from _what or _selector
      r←''
      :Trap 0
          :If 0∊⍴r←_what
          :AndIf '#'=⊃_selector
              r←1↓_selector
          :EndIf
      :EndTrap
    ∇

    ∇ r←renderContent content;c
      :Access public shared
      r←''
      content←eis content
      :Trap 0
          :While ~0∊⍴content
              :Select ≡c←⊃content
              :Case 0
                  :If isClass c
                      :Select ⊃⍴content
                      :Case 1
                          r,←(⎕NEW c).Render
                      :Case 2
                          r,←(⎕NEW c(2⊃content)).Render
                      :Else
                          r,←(⎕NEW c(1↓content)).Render
                      :EndSelect
                  :ElseIf isInstance c
                      r,←c.Render
                  :Else
                      r,←(⎕NEW #.HtmlElement(''content)).Render
                  :EndIf
                  content←''
              :Case 1
                  :If isClass⊃c
                      r,←(⎕NEW(⊃c)(1↓c)).Render
                  :ElseIf isInstance⊃c
                      ∘∘∘ ⍝ should not happen! (I think)
                  :Else
                      r,←(⎕NEW #.HtmlElement(''c)).Render
                  :EndIf
                  content←1↓content
              :Else
                  r,←renderContent c
                  content←1↓content
              :EndSelect
          :EndWhile
      :Else
          :If (⎕EN≥100)∧(⎕EN≤999) ⍝ HTTP status rather than an APL error
          :AndIf 0≠⎕NC⊂'_Request.Response' ⍝ we're actually running a server, not rendering in session
              _Request.Fail ⎕EN
          :Else ⍝ APL errors and in-session errors are not trapped:
              ⎕SIGNAL ⎕DMX.(⊂{⍵(⍎⍵)}¨(⎕NL-⍳9)~'InternalLocation' 'DM' 'OSError') ⍝ re-signal
          :EndIf
      :EndTrap
    ∇

    ∇ r←selector Replace content
      :Access public shared
      r←⊂('replace'selector)('data'(renderContent content))
    ∇
    ∇ r←selector Append content
      :Access public shared
      r←⊂('append'selector)('data'(renderContent content))
    ∇
    ∇ r←selector Prepend content
      :Access public shared
      r←⊂('prepend'selector)('data'(renderContent content))
    ∇
    ∇ r←Execute content
      :Access public shared
      r←⊂('execute'(renderContent content))
    ∇

    ∇ r←name Assign data
      :Access public shared
      r←⊂('assign'name)('data'data)
    ∇

    :endsection

    :section Position

    ∇ ref Position args;inds;mask;parameters;my;at;of;collision;within;q
      :Access public
      ⍝ ref  - a reference to an instance of anything based on HtmlElement
      ⍝ args - position information per jQueryUI's Position widget http://api.jqueryui.com/position/
      ⍝        can be in any of the following forms
      ⍝      1) positional (my at of collision within)  N.B. we don't use the "using" parameter
      ⍝         example:  myDiv Position 'left top' 'right bottom' '#otherElement'
      ⍝                   positions myDiv's top left corner at the bottom right corner of the element with id "otherElement"
      ⍝      2) paired
      ⍝                   myDiv Position 'my' 'left top' 'at' 'right bottom' 'of' '#otherElement'
      ⍝                   myDiv Position ('my' 'left top') ('at' 'right bottom') ('of' '#otherElement')
      ⍝                   myDiv Position 3 2⍴'my' 'left top' 'at' 'right bottom' 'of' '#otherElement'
      ⍝ Note: positional arguments are in form horizontal (left center right) vertical (top center bottom)

      parameters←'my' 'at' 'of' 'collision' 'within'
      q←{1⌽'''''',{⍵/⍨1+''''=⍵}⍕⍵}
      :If isInstance ref
          :If 2=⍴⍴args ⍝ matrix
              args←,args
          :ElseIf 3=≡args
              args←⊃,/args
          :EndIf
          args←eis args
          inds←parameters⍳args
          :If ∨/mask←inds≤⍴parameters
              :If mask≡(2×+/mask)⍴1 0
                  parameters←mask/args
                  args←(1⌽mask)/args
              :EndIf
          :Else
              parameters←(⍴args)↑parameters
          :EndIf
          mask←⍬∘≢¨args
          args←mask/args
          parameters←mask/parameters
          parameters(ref{⍺⍺⍎'Position.',⍺,'←',q ⍵})¨args
          ref.Uses,←⊂'JQueryUI'
          SetUse
      :EndIf
    ∇
    :endsection

    :section Event Handling Support
    ∇ r←isPost
      :Access public
      r←{0::0 ⋄ _Request.isPost}⍬
    ∇

    ∇ r←isAPLJax
      :Access public
      r←{0::0 ⋄ _Request.isAPLJAX}⍬
    ∇

    ∇ r←sel Css args ⍝ JQuery css cover
      :Access public
      r←(sel #._JSS.JQuery'css')args
    ∇

    ∇ r←sel Val args ⍝ JQuery val cover
      :Access public
      r←(sel #._JSS.JQuery'val')args
    ∇

    ∇ r←sel Prop args ⍝ JQuery prop cover
      :Access public
      r←(sel #._JSS.JQuery'prop')args
    ∇

    ∇ r←sel Attr args ⍝ JQuery attr cover
      :Access public
      r←(sel #._JSS.JQuery'attr')args
    ∇

    ∇ r←sel RemoveAttr args ⍝ JQuery removeAttr cover
      :Access public
      r←(sel #._JSS.JQuery'removeAttr')args
    ∇

    ∇ r←sel Html args ⍝ JQuery html cover
      :Access public
      r←(sel #._JSS.JQuery'html')args
    ∇

    ∇ r←sel Show args ⍝ JQuery show cover
      :Access public
      r←(sel #._JSS.JQuery'show')args
    ∇

    ∇ r←sel Hide args ⍝ JQuery hide cover
      :Access public
      r←(sel #._JSS.JQuery'hide')args
    ∇

    ∇ r←sel Toggle args ⍝ JQuery toggle cover
      :Access public
      r←(sel #._JSS.JQuery'toggle')args
    ∇

    ∇ r←Submit sel ⍝ JQuery submit cover
      :Access public
      r←(sel #._JSS.JQuery'submit')''
    ∇

    :endsection

:EndClass


fixed the format of an executeable Date to match the ⍎→⊂-change in MiServer

:Namespace _JSS ⍝ Javascript Snippets

    (⎕ML ⎕IO)←1
⍝ this list will grow over time as usage patterns are discovered
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    quote←{0∊⍴⍵: '' ⋄ '"',(('"' ⎕R '\\\0')⍕⍵),'"'}
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓∊{⍵,', '}¨eis ⍵}
    fmtData←#.JSON.fromAPL ⍝ {{(326=⍵)<0=2|⍵}⎕DR ⍵:quote ⍵ ⋄ ⍕⍵}
    JAchars←#.JSON.JAchars

    ∇ r←Alert txt
    ⍝ popup alert text
      r←'alert(',(quote txt),');'
    ∇

    ∇ r←CloseWindow
    ⍝ close the browser window
      r←'var w=window.open(window.location,''_self''); w.close();'
    ∇

    ∇ r←sel Css args ⍝ JQuery css cover
      r←(sel JQuery'css')args
    ∇

    ∇ r←sel Val args ⍝ JQuery val cover
      r←(sel JQuery'val')args
    ∇

    ∇ r←sel Prop args ⍝ JQuery prop cover
      r←(sel JQuery'prop')args
    ∇

    ∇ r←sel Attr args ⍝ JQuery attr cover
      r←(sel JQuery'attr')args
    ∇

    ∇ r←sel RemoveAttr args
      r←(sel JQuery'removeAttr')args
    ∇

    ∇ r←sel Html args ⍝ JQuery html cover
      r←(sel JQuery'html')args
    ∇

    ∇ r←sel Show args
      r←(sel JQuery'show')args
    ∇

    ∇ r←sel Hide args
      r←(sel JQuery'hide')args
    ∇

    ∇ r←sel Toggle args
      r←(sel JQuery'toggle')args
    ∇

    ∇ r←sel Trigger args
      r←(sel JQuery'trigger')args
    ∇

    ∇ r←Submit sel
      r←(sel JQuery'submit')''
    ∇

    ∇ r←sel Position args;Position;inds;parameters;q;mask
      parameters←'my' 'at' 'of' 'collision' 'within'
      q←{1⌽'''''',{⍵/⍨1+''''=⍵}⍕⍵}
      Position←⎕NS ⍬
      :If 2=⍴⍴args ⍝ matrix
          args←,args
      :ElseIf 3=≡args
          args←⊃,/args
      :EndIf
      args←eis args
      inds←parameters⍳args
      :If ∨/mask←inds≤⍴parameters
          :If mask≡(2×+/mask)⍴1 0
              parameters←mask/args
              args←(1⌽mask)/args
          :EndIf
      :Else
          parameters←(⍴args)↑parameters
      :EndIf
      mask←⍬∘≢¨args
      args←mask/args
      parameters←mask/parameters
      parameters({⍎'Position.',⍺,'←',q ⍵})¨args
      :If ~0∊⍴Position.⎕NL-2
          r←0 #.JQ.JQueryfn'position'sel Position
      :EndIf
    ∇

    ∇ r←{eval}JSDate date
    ⍝ snippet to create a JS date (JavaScript months are 0-11!!!)
    ⍝ date is in 6↑⎕TS form
    ⍝ eval is optional Boolean to indicate whether to prepend '⍎' to the snippet (default is 0=do not prepend)
    ⍝         prepending ⍎ tells MiServer that this is an executable phrase and not simply a string

      :If 0=⎕NC'eval' ⋄ eval←0 ⋄ :EndIf
      :If 0 2∊⍨10|⎕DR date  ⍝ char?
          date←'"',date,'"'
      :Else
          date←1↓∊','∘,∘⍕¨0 ¯1 0 0 0 0+6↑date
      :EndIf
⍝      r←(~eval)↓'⍎new Date(',date,')'
⍝ ⍝ 180221, MBaas: ⍎ no longer supported
      :If eval
          r←⊂'new Date(',date,')'
      :Else
          r←'new Date(',date,')'
      :EndIf
    ∇

    ∇ r←{varname}JSData data
    ⍝ return var definition to build a JavaScript object based on data
    ⍝ varname is the name of the variable
    ⍝ data is either a matrix of [1;] column names, (1↓) data
    ⍝      or a vector of namespaces
      :If 0=⎕NC'varname' ⋄ varname←'' ⋄ :EndIf
      :If 2=⍴⍴data
          data←#.JSON.fromTable data
      :EndIf
      r←(((~0∊⍴varname)/'var ',varname,' = '),0 0 1 #.JSON.fromAPL data),';'
    ∇

    ∇ r←{val}(sel JQuery fn)args;opt
    ⍝ construct JavaScript to call a jQuery function - eg val(), html(), css(), prop(), or attr()
    ⍝ optionally setting a value for
    ⍝ Get a jQuery parameter:
    ⍝    ('"#id"' JQuery 'attr') '"data-role"'
    ⍝ Set a jQuery parameter:
    ⍝    '"blue"' ('#id' JQuery 'css') 'background-color'
    ⍝
      args←eis args
      :If 0=⎕NC'val'
          (opt val)←2↑args,(⍴args)↓''⎕NULL
      :Else
          opt←⊃args
      :EndIf
      :If val≡⎕NULL
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    ∇ r←{val}(fn JQueryOpt sel)opt
      :If 0=⎕NC'val'
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    :Class StorageObject
    ⍝ !!!Prototype!!!

        ∇ r←{what}Set(type value);name;w;v
          :Access public shared
        ⍝ value may be
          :Access public shared
          r←''
          :If 9.1=⎕NC⊂'value' ⍝ namespace?
              :For name :In value.⎕NL-2
                  r,←formatSet(type name(value⍎name))
              :EndFor
          :Else
              value←eis value
              :If 2=⎕NC'what' ⍝ value is list of name value pairs
                  what←eis what
              :Else
                  (what value)←↓[1]((⌊0.5×⍴value),2)⍴value
              :EndIf
              :For (w v) :InEach (what value)
                  r,←formatSet(type w v)
              :EndFor
          :EndIf
        ∇

        ∇ r←formatSet(type what value)
          :Access public shared
          r←type,'.setItem("',what,'",JSON.stringify(',(#.JSON.fromAPL value),');'
        ∇

        ∇ r←type Remove what;w;ww
          :Access public shared
          what←eis what
          r←''
          :For w :In what
              :If ' '∊w←#.Strings.deb w
                  :For ww :In ' '#.Utils.penclose w
                      r,←type,'.removeItem("',w,'");'
                  :EndFor
              :Else
                  r,←type,'.removeItem("',w,'");'
              :EndIf
          :EndFor
        ∇

        ∇ r←type Get what
          :Access public shared
          r←''
          r,←'<input type="hidden" name="',w,'" value="',JSON
        ∇
    :endclass

    :class localStorage : StorageObject

        ∇ r←{what}Set value
          :If 0=⎕NC'what'
              r←⎕BASE.Set('localStorage'value)
          :Else
              r←what ⎕BASE.Set('localStorage'value)
          :EndIf
        ∇

        ∇ r←Remove what
          :Access public shared
          r←
        ∇

        ∇ r←{name}Get what
          :Access public shared
        ∇

    :EndClass

    :Class sessionStorage
        ∇ r←{what}Set value
          :Access public shared
        ∇
    :endclass

    :class sessionStorage
    :endclass

:EndNamespace

:Namespace _JSS ⍝ Javascript Snippets

    (⎕ML ⎕IO)←1
⍝ this list will grow over time as usage patterns are discovered
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    quote←{0∊⍴⍵: '' ⋄ '"',(('"' ⎕R '\\\0')⍕⍵),'"'}
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓∊{⍵,', '}¨eis ⍵}
    fmtData←#.JSON.fromAPL ⍝ {{(326=⍵)<0=2|⍵}⎕DR ⍵:quote ⍵ ⋄ ⍕⍵}
    JAchars←#.JSON.JAchars

    ∇ r←Alert txt
    ⍝ popup alert text
      r←'alert(',(quote txt),');'
    ∇

    ∇ r←CloseWindow
    ⍝ close the browser window
      r←'var w=window.open(window.location,''_self''); w.close();'
    ∇

    ∇ r←sel Css args ⍝ JQuery css cover
      r←(sel JQuery'css')args
    ∇

    ∇ r←sel Val args ⍝ JQuery val cover
      r←(sel JQuery'val')args
    ∇

    ∇ r←sel Prop args ⍝ JQuery prop cover
      r←(sel JQuery'prop')args
    ∇

    ∇ r←sel Attr args ⍝ JQuery attr cover
      r←(sel JQuery'attr')args
    ∇

    ∇ r←sel RemoveAttr args
      r←(sel JQuery'removeAttr')args
    ∇

    ∇ r←sel Html args ⍝ JQuery html cover
      r←(sel JQuery'html')args
    ∇

    ∇ r←sel Show args
      r←(sel JQuery'show')args
    ∇

    ∇ r←sel Hide args
      r←(sel JQuery'hide')args
    ∇

    ∇ r←sel Toggle args
      r←(sel JQuery'toggle')args
    ∇

    ∇ r←sel Trigger args
      r←(sel JQuery'trigger')args
    ∇

    ∇ r←Submit sel
      r←(sel JQuery'submit')''
    ∇

    ∇ r←sel Position args;Position;inds;parameters;q;mask
      parameters←'my' 'at' 'of' 'collision' 'within'
      q←{1⌽'''''',{⍵/⍨1+''''=⍵}⍕⍵}
      Position←⎕NS ⍬
      :If 2=⍴⍴args ⍝ matrix
          args←,args
      :ElseIf 3=≡args
          args←⊃,/args
      :EndIf
      args←eis args
      inds←parameters⍳args
      :If ∨/mask←inds≤⍴parameters
          :If mask≡(2×+/mask)⍴1 0
              parameters←mask/args
              args←(1⌽mask)/args
          :EndIf
      :Else
          parameters←(⍴args)↑parameters
      :EndIf
      mask←⍬∘≢¨args
      args←mask/args
      parameters←mask/parameters
      parameters({⍎'Position.',⍺,'←',q ⍵})¨args
      :If ~0∊⍴Position.⎕NL-2
          r←0 #.JQ.JQueryfn'position'sel Position
      :EndIf
    ∇

    ∇ r←{eval}JSDate date
    ⍝ snippet to create a JS date (JavaScript months are 0-11!!!)
    ⍝ date is in 6↑⎕TS form
    ⍝ eval is optional Boolean to indicate whether to prepend '⍎' to the snippet (default is 0=do not prepend)
    ⍝         prepending ⍎ tells MiServer that this is an executable phrase and not simply a string

      :If 0=⎕NC'eval' ⋄ eval←0 ⋄ :EndIf
      :If 0 2∊⍨10|⎕DR date  ⍝ char?
          date←'"',date,'"'
      :Else
          date←1↓∊','∘,∘⍕¨0 ¯1 0 0 0 0+6↑date
      :EndIf
      r←(~eval)↓'⍎new Date(',date,')'
    ∇

    ∇ r←{varname}JSData data
    ⍝ return var definition to build a JavaScript object based on data
    ⍝ varname is the name of the variable
    ⍝ data is either a matrix of [1;] column names, (1↓) data
    ⍝      or a vector of namespaces
      :If 0=⎕NC'varname' ⋄ varname←'' ⋄ :EndIf
      :If 2=⍴⍴data
          data←#.JSON.fromTable data
      :EndIf
      r←(((~0∊⍴varname)/'var ',varname,' = '),0 0 1 #.JSON.fromAPL data),';'
    ∇

    ∇ r←{val}(sel JQuery fn)args;opt
    ⍝ construct JavaScript to call a jQuery function - eg val(), html(), css(), prop(), or attr()
    ⍝ optionally setting a value for
    ⍝ Get a jQuery parameter:
    ⍝    ('"#id"' JQuery 'attr') '"data-role"'
    ⍝ Set a jQuery parameter:
    ⍝    '"blue"' ('#id' JQuery 'css') 'background-color'
    ⍝
      args←eis args
      :If 0=⎕NC'val'
          (opt val)←2↑args,(⍴args)↓''⎕NULL
      :Else
          opt←⊃args
      :EndIf
      :If val≡⎕NULL
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    ∇ r←{val}(fn JQueryOpt sel)opt
      :If 0=⎕NC'val'
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    :Class StorageObject
    ⍝ !!!Prototype!!!

        ∇ r←{what}Set(type value);name;w;v
          :Access public shared
        ⍝ value may be
          :Access public shared
          r←''
          :If 9.1=⎕NC⊂'value' ⍝ namespace?
              :For name :In value.⎕NL-2
                  r,←formatSet(type name(value⍎name))
              :EndFor
          :Else
              value←eis value
              :If 2=⎕NC'what' ⍝ value is list of name value pairs
                  what←eis what
              :Else
                  (what value)←↓[1]((⌊0.5×⍴value),2)⍴value
              :EndIf
              :For (w v) :InEach (what value)
                  r,←formatSet(type w v)
              :EndFor
          :EndIf
        ∇

        ∇ r←formatSet(type what value)
          :Access public shared
          r←type,'.setItem("',what,'",JSON.stringify(',(#.JSON.fromAPL value),');'
        ∇

        ∇ r←type Remove what;w;ww
          :Access public shared
          what←eis what
          r←''
          :For w :In what
              :If ' '∊w←#.Strings.deb w
                  :For ww :In ' '#.Utils.penclose w
                      r,←type,'.removeItem("',w,'");'
                  :EndFor
              :Else
                  r,←type,'.removeItem("',w,'");'
              :EndIf
          :EndFor
        ∇

        ∇ r←type Get what
          :Access public shared
          r←''
          r,←'<input type="hidden" name="',w,'" value="',JSON
        ∇
    :endclass

    :class localStorage : StorageObject

        ∇ r←{what}Set value
          :If 0=⎕NC'what'
              r←⎕BASE.Set('localStorage'value)
          :Else
              r←what ⎕BASE.Set('localStorage'value)
          :EndIf
        ∇

        ∇ r←Remove what
          :Access public shared
          r←
        ∇

        ∇ r←{name}Get what
          :Access public shared
        ∇

    :EndClass

    :Class sessionStorage
        ∇ r←{what}Set value
          :Access public shared
        ∇
    :endclass

    :class sessionStorage
    :endclass

:EndNamespace


Improve SQL.Do bind variable handling

Improve SQL.Do bind variable handling

:Namespace SQL
  (⎕IO ⎕ML)←1

  ∇ r←ConnectTo database;ind;ds;dsn;opts;rc;conx;pwd;user;ms;find
    ms←#.Boot.ms
    find←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⊂⍵}
    r←601 'No datasources defined'
    :If 0<⍴{6::'' ⋄ ⍵.Datasources}ms ⍝ do we have Datasources defined?
      :If 0=ind←ms.Datasources.Name find database ⍝ try to find it
        r←601 ''('Datasource "',database,'" not found')
        :Return
      :Else ⍝ found it
        ds←ind⊃ms.Datasources
        r←601 'SQAPL not available'
        :If 0=⊃#.SQA.Init''
          (dsn opts user pwd)←{6::'' ⋄ ⍎⍵}¨'ds.'∘,¨'DSN' 'DriverOptions' 'User' 'Password'
          conx←{⊃('C'∘,¨⍕¨⍳1+⍴⍵)~⍵}⊃¨2 2⊃#.SQA.Tree'.'
          :If 0=1⊃rc←#.SQA.Connect conx dsn pwd user('DriverOptions'(opts)) ⋄ r←0 conx
          :Else ⋄ r←601('Unable to connect to "',database,'"')
          :EndIf
        :EndIf
      :EndIf
    :EndIf
  ∇

  ∇ r←Do ctl;c;m;z;ns;conx;data
  ⍝ Prepare, Execute, Fetch a statement.
  ⍝       ctl: database sqlstmt {bindvars}

    r←⎕NS''
    →(0<1⊃z←ConnectTo(1⊃ctl))⍴l3 ⋄ conx←2⊃z
    →(0<1⊃z←#.SQA.CursorName conx)⍴l3 ⋄ c←2⊃z
    →(0<1⊃z←#.SQA.Prepare c(2⊃ctl))⍴l3
    :If 1<|≡3⊃ctl ⋄ data←3⊃ctl ⋄ :Else ⋄ data←2↓ctl ⋄ :EndIf
    →(0<1⊃z←#.SQA.Exec(⊂c),data)⍴l2
    →(0<1⊃z←0 #.SQA.Describe c)⍴l2 ⋄ r.Columns←{(0=(⊂1 3)⊃¨⍵)/(⊂1 1)⊃¨⍵}2 2⊃z
    →(0<2 1 5⊃z)⍴l1        ⍝ Fetch if there are Bind Vars
    r.(ReturnCode Data)←0(0 0⍴0) ⋄ →l2
   l1:→(0<1⊃z←1 #.SQA.Fetch c)⍴l2
    r.ReturnCode←1⊃z
    r.Data←⊃2⊃z
   l2:→(0<1⊃z←#.SQA.Close conx)↓0
   l3:r.ReturnCode←1⊃z
  ∇

  ∇ r←CloseAll
    →(0∊⍴r←2 2⊃#.SQA.Tree'.')⍴0
    r←#.SQA.Close¨1⊃¨r
  ∇

:EndNamespace

:Namespace SQL
    (⎕IO ⎕ML)←1

    ∇ r←ConnectTo database;ind;ds;dsn;opts;rc;conx;pwd;user;ms;find
      ms←#.Boot.ms
      find←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⊂⍵}
      r←601 'No datasources defined'
      :If 0<⍴{6::'' ⋄ ⍵.Datasources}ms ⍝ do we have Datasources defined?
          :If 0=ind←ms.Datasources.Name find database ⍝ try to find it
              r←601 ''('Datasource "',database,'" not found')
              :Return
          :Else ⍝ found it
              ds←ind⊃ms.Datasources
              r←601 'SQAPL not available'
              :If 0=⊃#.SQA.Init''
                  (dsn opts user pwd)←{6::'' ⋄ ⍎⍵}¨'ds.'∘,¨'DSN' 'DriverOptions' 'User' 'Password'
                  conx←{⊃('C'∘,¨⍕¨⍳1+⍴⍵)~⍵}⊃¨2 2⊃#.SQA.Tree'.'
                  :If 0=1⊃rc←#.SQA.Connect conx dsn pwd user('DriverOptions'(opts)) ⋄ r←0 conx
                  :Else ⋄ r←601('Unable to connect to "',database,'"')
                  :EndIf
              :EndIf
          :EndIf
      :EndIf
    ∇

    ∇ r←Do ctl;c;m;z;ns;conx
  ⍝ Prepare, Execute, Fetch a statement.
  ⍝       ctl: database sqlstmt {bindvars}

      r←⎕NS''
      →(0<1⊃z←ConnectTo(1⊃ctl))⍴l3 ⋄ conx←2⊃z
      →(0<1⊃z←#.SQA.CursorName conx)⍴l3 ⋄ c←2⊃z
      →(0<1⊃z←#.SQA.Prepare c(2⊃ctl))⍴l3
      →(0<1⊃z←#.SQA.Exec(⊂c),2↓ctl)⍴l2
      →(0<1⊃z←0 #.SQA.Describe c)⍴l2 ⋄ r.Columns←{(0=(⊂1 3)⊃¨⍵)/(⊂1 1)⊃¨⍵}2 2⊃z
      →(0<2 1 5⊃z)⍴l1        ⍝ Fetch if there are Bind Vars
      r.(ReturnCode Data)←0(0 0⍴0) ⋄ →l2
     l1:→(0<1⊃z←1 #.SQA.Fetch c)⍴l2
      r.ReturnCode←1⊃z
      r.Data←⊃2⊃z
     l2:→(0<1⊃z←#.SQA.Close conx)↓0
     l3:r.ReturnCode←1⊃z
    ∇

    ∇ r←CloseAll
      →(0∊⍴r←2 2⊃#.SQA.Tree'.')⍴0
      r←#.SQA.Close¨1⊃¨r
    ∇

:EndNamespace


Throw the correct errors from matrix inverse, as specified in the ISO standard.