Make HTTPCommand.b64Encode public

Make HTTPCommand.b64Encode public

:Class HTTPCommand
⍝ Issue an HTTP Command
⍝ Public Fields are:
⍝   Command  - the HTTP command to issue, typically 'GET' 'POST' 'PUT etc
⍝   URL      - the URL to direct the command at
⍝              format is:  [HTTP[S]://][user:pass@]url[:port][/page[?params]]
⍝   Params   - the parameters to pass with the command
⍝              this can either be a URLEncoded character vector, or a namespace containing the named parameters
⍝              for 'GET' you can pass parameters either in the URL or in Params (they will be appended to the URL)
⍝   Headers  - any additional HTTP headers to send with the request (all the obvious headers like 'content-length' are precomputed)
⍝              a vector of 2-element (header-name value) vectors or a matrix of [;1] header-name [;2] values
⍝   Cert     - if using a SSL, this is an instance of the X509Cert class
⍝   SSLFlags - if using SSL, these are the SSL flags as described in the Conga documentation
⍝   Priority - if using SSL, this is the GNU TLS priority string (generally you won't change this from the default)
⍝   LocalDRC - if set, this is a reference to the DRC namespace from Conga - otherwise, we look for DRC in the workspace root
⍝   WaitTime - time (in seconds) to wait for the response (default 30)
⍝
⍝   Methods Run and Get return a namespace containing the variables:
⍝   data          - the response message payload
⍝   httpver       - the server HTTP version
⍝   httpstatus    - the response HTTP status code (200 means OK)
⍝   httpstatusmsg - the response HTTP status message
⍝   headers       - the response HTTP headers
⍝   peercert      - the server (peer) certificate if running secure
⍝   rc            - the Conga return code (0 means no error)

    ⎕ML←⎕IO←1

    :field public Command←'GET'
    :field public Cert←⍬
    :field public SSLFlags←32
    :field public shared LocalDRC←''
    :field public URL←''
    :field public Params←''
    :field public Headers←''
    :field public Priority←'NORMAL:!CTYPE-OPENPGP'
    :field public shared WaitTime←30

    ∇ make
      :Access public
      :Implements constructor
    ∇

    ∇ make1 args
      :Access public
      :Implements constructor
      ⍝ args - [Command URL Params Headers Cert SSLFlags Priority]
      args←eis args
      Command URL Params Headers Cert SSLFlags Priority←7↑args,(⍴args)↓Command URL Params Headers Cert SSLFlags Priority
    ∇

    ∇ r←Run
      :Access public
      :If 0∊⍴Cert
          r←(Command HTTPCmd)URL Params Headers
      :Else
          r←(Cert SSLFlags Priority)(Command HTTPCmd)URL Params Headers
      :EndIf
    ∇

    ∇ r←Get args
      :Access public shared
      r←('GET'HTTPCmd)args
    ∇

    ∇ r←{certs}Do args
      :Access public shared
    ⍝ args is Command URL Params Headers Cert SSLFlags Priority
      args←eis args
      :If 0=⎕NC'certs'
          r←((⊃args)HTTPCmd)1↓args
      :Else
          r←certs((⊃args)HTTPCmd)1↓args
      :EndIf
    ∇

    ∇ r←{certs}(cmd HTTPCmd)args;url;parms;hdrs;urlparms;p;b;secure;port;host;page;x509;flags;priority;pars;auth;req;err;chunked;chunk;buffer;chunklength;done;data;datalen;header;headerlen;status;httpver;httpstatus;httpstatusmsg;rc;dyalog;FileSep;donetime;congaCopied;peercert
⍝ issue an HTTP command
⍝ certs - optional [X509Cert [SSLValidation [Priority]]]
⍝ args  - [1] URL in format [HTTP[S]://][user:pass@]url[:port][/page]
⍝         {2} parameters is using POST - either a namespace or URL-encoded string
⍝         {3} HTTP headers in form {↑}(('hdr1' 'val1')('hdr2' 'val2'))
⍝ Makes secure connection if left arg provided or URL begins with https:

⍝ Result: (conga return code) (HTTP Status) (HTTP headers) (HTTP body) [PeerCert if secure]
      r←⎕NS''
      (rc httpver httpstatus httpstatusmsg header data peercert)←¯1 '' 400(⊂'bad request')(0 2⍴⊂'')''⍬

      congaCopied←0
      :If 0∊⍴LocalDRC
          :Select ⊃#.⎕NC'DRC'
          :Case 9
              LDRC←#.DRC
          :Case 0
              FileSep←'/\'[1+'Win'≡3↑1⊃#.⎕WG'APLVersion']
              dyalog←{⍵,(-FileSep=¯1↑⍵)↓FileSep}2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'
              'DRC'⎕CY dyalog,'ws/conga' ⍝ runtime needs full workspace path
              :If {0::1 ⋄ 0⊣⍎'DRC'}''
                  ⎕←'Conga namespace DRC not found or defined'
                  →0
              :EndIf
              LDRC←DRC
              congaCopied←1
          :Else
              ⎕←'Conga namespace DRC not found or defined'
              →0
          :EndSelect
      :ElseIf 9=⎕NC'LocalDRC'
          LDRC←LocalDRC
      :Else
          LDRC←⍎⍕LocalDRC
      :EndIf

      {}LDRC.Init''
      args←eis args

      (url parms hdrs)←args,(⍴args)↓''(⎕NS'')''
      urlparms←''
      cmd←uc,cmd

      :If 'GET'≡cmd
          urlparms←parms
          parms←''
      :EndIf

      parms←URLEncode parms
      urlparms←{0∊⍴⍵:'' ⋄ ('?'=1↑⍵)↓'?',⍵}URLEncode urlparms

     GET:
      p←(∨/b)×1+(b←'//'⍷url)⍳1
      secure←{6::⍵ ⋄ ⍵∨0<⍴,certs}(lc(p-2)↑url)≡'https:'
      port←(1+secure)⊃80 443 ⍝ Default HTTP/HTTPS port
      url←p↓url              ⍝ Remove HTTP[s]:// if present
      (host page)←'/'split url,(~'/'∊url)/'/'    ⍝ Extract host and page from url

      :If 0=⎕NC'certs' ⋄ certs←'' ⋄ :EndIf

      :If secure
          x509 flags priority←3↑certs,(⍴,certs)↓(⎕NEW LDRC.X509Cert)32 'NORMAL:!CTYPE-OPENPGP'  ⍝ 32=Do not validate Certs
          pars←('x509'x509)('SSLValidation'flags)('Priority'priority)
      :Else ⋄ pars←''
      :EndIf

      :If '@'∊host ⍝ Handle user:password@host...
          auth←'Authorization: Basic ',(b64Encode(¯1+p←host⍳'@')↑host),NL
          host←p↓host
      :Else ⋄ auth←''
      :EndIf

      (host port)←port{(⍴⍵)<ind←⍵⍳':':⍵ ⍺ ⋄ (⍵↑⍨ind-1)(1⊃2⊃⎕VFI ind↓⍵)}host ⍝ Check for override of port number

      hdrs←makeHeaders hdrs
      hdrs←'User-Agent'(hdrs addHeader)'Dyalog/Conga'
      hdrs←'Accept'(hdrs addHeader)'*/*'

      :If ~0∊⍴parms
          :If cmd≡'POST'
              hdrs←'Content-Length'(hdrs addHeader)⍴parms
              hdrs←'Content-Type'(hdrs addHeader)'application/x-www-form-urlencoded'
          :EndIf
      :EndIf

      hdrs←'Accept-Encoding'(hdrs addHeader)'gzip, deflate'

      req←(uc cmd),' ',(page,urlparms),' HTTP/1.1',NL,'Host: ',host,NL
      req,←fmtHeaders hdrs
      req,←auth

      donetime←⌊⎕AI[3]+1000×WaitTime ⍝ time after which we'll time out

      :If 0=⊃(err cmd)←2↑rc←LDRC.Clt''host port'Text' 100000,pars ⍝ 100,000 is max receive buffer size
      :AndIf 0=⊃rc←LDRC.Send cmd(req,NL,parms)

          chunked chunk buffer chunklength←0 '' '' 0
          done data datalen headerlen header←0 ⍬ 0 0 ⍬
          :Repeat
              :If ~done←0≠1⊃rc←LDRC.Wait cmd 5000            ⍝ Wait up to 5 secs
                  :If rc[3]∊'Block' 'BlockLast'             ⍝ If we got some data
                      :If chunked
                          chunk←4⊃rc
                      :ElseIf 0<⍴data,←4⊃rc
                      :AndIf 0=headerlen
                          (headerlen header)←DecodeHeader data
                          :If 0<headerlen
                              data←headerlen↓data
                              :If chunked←∨/'chunked'⍷header getHeader'Transfer-Encoding'
                                  chunk←data
                                  data←''
                              :Else
                                  datalen←⊃(toNum header getHeader'Content-Length'),¯1 ⍝ ¯1 if no content length not specified
                              :EndIf
                          :EndIf
                      :EndIf
                  :Else
                      ⎕←rc ⍝ Error?
                      ∘∘∘  ⍝ !! Intentional !!
                  :EndIf
                  :If chunked
                      buffer,←chunk
                      :While done<¯1≠⊃(len chunklength)←getchunklen buffer
                          :If (⍴buffer)≥4+len+chunklength
                              data,←chunklength↑(len+2)↓buffer
                              buffer←(chunklength+len+4)↓buffer
                              :If done←0=chunklength ⍝ chunked transfer can add headers at the end of the transmission
                                  header←header⍪2⊃DecodeHeader buffer
                              :EndIf
                          :EndIf
                      :EndWhile
                  :Else
                      done←done∨'BlockLast'≡3⊃rc                        ⍝ Done if socket was closed
                      :If datalen>0
                          done←done∨datalen≤⍴data ⍝ ... or if declared amount of data rcvd
                      :Else
                          done←done∨(∨/''⍷data)∨(∨/''⍷data)
                      :EndIf
                  :EndIf
              :ElseIf 100=1⊃rc ⍝ timeout?
                  done←⎕AI[3]>donetime
              :EndIf
          :Until done

          :If 0=1⊃rc
              :Trap 0 ⍝ If any errors occur, abandon conversion
                  :Select header getHeader'content-encoding' ⍝ was the response compressed?
                  :Case 'deflate'
                      data←fromutf8 LDRC.flate.Inflate 120 156{(2×⍺≡2↑⍵)↓⍺,⍵}256|83 ⎕DR data ⍝ append 120 156 signature because web servers strip it out due to IE
                  :Case 'gzip'
                      data←fromutf8 256|¯3(219⌶)83 ⎕DR data
                  :Else
                      :If ∨/'charset=utf-8'⍷header getHeader'content-type'
                          data←'UTF-8'⎕UCS ⎕UCS data ⍝ Convert from UTF-8
                      :EndIf
                  :EndSelect

                  :If {(⍵[3]∊'12357')∧'30 '≡⍵[1 2 4]}4↑{⍵↓⍨⍵⍳' '}(⊂1 1)⊃header ⍝ redirected? (HTTP status codes 301, 302, 303, 305, 307)
                      →GET⍴⍨0<⍴url←'location'{(⍵[;1]⍳⊂⍺)⊃⍵[;2],⊂''}header ⍝ use the "location" header field for the URL
                  :EndIf

              :EndTrap

              httpver httpstatus httpstatusmsg←{⎕ML←3 ⋄ ⍵⊂⍨{⍵∨2<+\~⍵}⍵≠' '}(⊂1 1)⊃header
              header↓⍨←1

              :If secure ⋄ peercert←⊂LDRC.GetProp cmd'PeerCert' ⋄ :EndIf
          :EndIf

          r.(rc httpver httpstatus httpstatusmsg headers data peercert)←(1⊃rc)httpver(toNum httpstatus)httpstatusmsg header data peercert

      :Else
          ⎕←'Connection failed ',,⍕rc
          r.rc←1⊃rc
      :EndIf

      {}LDRC.Close cmd

      :If congaCopied
          {}LDRC.Close'.'
          LDRC.(⎕EX¨⍙naedfns)
      :EndIf
    ∇

    NL←⎕UCS 13 10
    fromutf8←{0::(⎕AV,'?')[⎕AVU⍳⍵] ⋄ 'UTF-8'⎕UCS ⍵} ⍝ Turn raw UTF-8 input into text
    utf8←{3=10|⎕DR ⍵: 256|⍵ ⋄ 'UTF-8' ⎕UCS ⍵}
    sint←{⎕io←0 ⋄ 83=⎕DR ⍵:⍵ ⋄ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 ¯128 ¯127 ¯126 ¯125 ¯124 ¯123 ¯122 ¯121 ¯120 ¯119 ¯118 ¯117 ¯116 ¯115 ¯114 ¯113 ¯112 ¯111 ¯110 ¯109 ¯108 ¯107 ¯106 ¯105 ¯104 ¯103 ¯102 ¯101 ¯100 ¯99 ¯98 ¯97 ¯96 ¯95 ¯94 ¯93 ¯92 ¯91 ¯90 ¯89 ¯88 ¯87 ¯86 ¯85 ¯84 ¯83 ¯82 ¯81 ¯80 ¯79 ¯78 ¯77 ¯76 ¯75 ¯74 ¯73 ¯72 ¯71 ¯70 ¯69 ¯68 ¯67 ¯66 ¯65 ¯64 ¯63 ¯62 ¯61 ¯60 ¯59 ¯58 ¯57 ¯56 ¯55 ¯54 ¯53 ¯52 ¯51 ¯50 ¯49 ¯48 ¯47 ¯46 ¯45 ¯44 ¯43 ¯42 ¯41 ¯40 ¯39 ¯38 ¯37 ¯36 ¯35 ¯34 ¯33 ¯32 ¯31 ¯30 ¯29 ¯28 ¯27 ¯26 ¯25 ¯24 ¯23 ¯22 ¯21 ¯20 ¯19 ¯18 ¯17 ¯16 ¯15 ¯14 ¯13 ¯12 ¯11 ¯10 ¯9 ¯8 ¯7 ¯6 ¯5 ¯4 ¯3 ¯2 ¯1[utf8 ⍵]}
    lc←(819⌶) ⍝ lower case conversion
    uc←1∘lc
    dlb←{(+/∧\' '=⍵)↓⍵} ⍝ delete leading blanks
    split←{(p↑⍵)((p←¯1+⍵⍳⍺)↓⍵)} ⍝ split ⍵ on first occurrence of ⍺
    h2d←{⎕IO←0 ⋄ 16⊥'0123456789abcdef'⍳lc ⍵} ⍝ hex to decimal
    getchunklen←{¯1=len←¯1+⊃(NL⍷⍵)/⍳⍴⍵:¯1 ¯1 ⋄ chunklen←h2d len↑⍵ ⋄ (⍴⍵)<len+chunklen+4:¯1 ¯1 ⋄ len chunklen}
    eis←{⍺←1 ⋄ ,(⊂⍣(⍺=|≡⍵))⍵} ⍝ enclose if simple
    toNum←{0∊⍴⍵:⍬ ⋄ 1⊃2⊃⎕VFI ⍕⍵}
    getHeader←{(⍺[;2],⊂'∘↑∘')⊃⍨(lc ¨⍺[;1])⍳eis lc ⍵}
    addHeader←{'∘↑∘'≡⍺⍺ getHeader ⍺:⍺⍺⍪⍺ ⍵ ⋄ ⍺⍺} ⍝ add a header unless it's already defined
    makeHeaders←{⎕ML←1 ⋄ 0∊⍴⍵:0 2⍴⊂'' ⋄ 2=⍴⍴⍵:⍵ ⋄ ↑2 eis ⍵}
    fmtHeaders←{⎕ML←1 ⋄ 0∊⍴⍵:'' ⋄ ∊{0∊⍴2⊃⍵:'' ⋄ NL,⍨(firstCaps 1⊃⍵),': ',⍕2⊃⍵}¨↓⍵}
    firstCaps←{1↓{(¯1↓0,'-'=⍵) (819⌶)¨ ⍵}'-',⍵}
    ∇ r←b64Encode w
    ⍝ Base64 Encode
      :Access public shared
      r←{raw←⊃,/11∘⎕DR¨⍵
         cols←6
         rows←⌈(⊃⍴raw)÷cols
         mat←rows cols⍴(rows×cols)↑raw
         'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[⎕IO+2⊥⍉mat],(4|-rows)⍴'='}w
    ∇

    ∇ r←DecodeHeader buf;len;d;i
      ⍝ Decode HTTP Header
      r←0(0 2⍴⊂'')
      :If 0<i←⊃{((NL,NL)⍷⍵)/⍳⍴⍵}buf
          len←(¯1+⍴NL,NL)+i
          d←(⍴NL)↓¨{(NL⍷⍵)⊂⍵}NL,len↑buf
          d←↑{((p-1)↑⍵)((p←⍵⍳':')↓⍵)}¨d
          d[;1]←lc¨d[;1]
          d[;2]←dlb¨d[;2]
          r←len d
      :EndIf
    ∇

    ∇ r←{name}URLEncode data;⎕IO;z;ok;nul;m;enlist;noname
      :Access Public Shared
      noname←0
      :If 9.1=⎕NC⊂'data'
          data←{0∊⍴t←⍵.⎕NL ¯2:'' ⋄ ↑⍵{⍵(⍕,⍺⍎⍵)}¨t}data
      :Else
          :If 1≥|≡data
              :If noname←0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
              data←name data
          :EndIf
      :EndIf
      nul←⎕UCS ⎕IO←0
      enlist←{⎕ML←1 ⋄ ∊⍵}
      ok←nul,enlist ⎕UCS¨(⎕UCS'aA0')+⍳¨26 26 10

      z←⎕UCS'UTF-8'⎕UCS enlist nul,¨,data
      :If ∨/m←~z∊ok
          (m/z)←↓'%',(⎕D,⎕A)[⍉16 16⊤⎕UCS m/z]
          data←(⍴data)⍴1↓¨{(⍵=nul)⊂⍵}enlist z
      :EndIf

      r←noname↓¯1↓enlist data,¨(⍴data)⍴'=&'
    ∇

:EndClass

:Class HTTPCommand
⍝ Issue an HTTP Command
⍝ Public Fields are:
⍝   Command  - the HTTP command to issue, typically 'GET' 'POST' 'PUT etc
⍝   URL      - the URL to direct the command at
⍝              format is:  [HTTP[S]://][user:pass@]url[:port][/page[?params]]
⍝   Params   - the parameters to pass with the command
⍝              this can either be a URLEncoded character vector, or a namespace containing the named parameters
⍝              for 'GET' you can pass parameters either in the URL or in Params (they will be appended to the URL)
⍝   Headers  - any additional HTTP headers to send with the request (all the obvious headers like 'content-length' are precomputed)
⍝              a vector of 2-element (header-name value) vectors or a matrix of [;1] header-name [;2] values
⍝   Cert     - if using a SSL, this is an instance of the X509Cert class
⍝   SSLFlags - if using SSL, these are the SSL flags as described in the Conga documentation
⍝   Priority - if using SSL, this is the GNU TLS priority string (generally you won't change this from the default)
⍝   LocalDRC - if set, this is a reference to the DRC namespace from Conga - otherwise, we look for DRC in the workspace root
⍝   WaitTime - time (in seconds) to wait for the response (default 30)
⍝
⍝   Methods Run and Get return a namespace containing the variables:
⍝   data          - the response message payload
⍝   httpver       - the server HTTP version
⍝   httpstatus    - the response HTTP status code (200 means OK)
⍝   httpstatusmsg - the response HTTP status message
⍝   headers       - the response HTTP headers
⍝   peercert      - the server (peer) certificate if running secure
⍝   rc            - the Conga return code (0 means no error)

    ⎕ML←⎕IO←1

    :field public Command←'GET'
    :field public Cert←⍬
    :field public SSLFlags←32
    :field public shared LocalDRC←''
    :field public URL←''
    :field public Params←''
    :field public Headers←''
    :field public Priority←'NORMAL:!CTYPE-OPENPGP'
    :field public shared WaitTime←30

    ∇ make
      :Access public
      :Implements constructor
    ∇

    ∇ make1 args
      :Access public
      :Implements constructor
      ⍝ args - [Command URL Params Headers Cert SSLFlags Priority]
      args←eis args
      Command URL Params Headers Cert SSLFlags Priority←7↑args,(⍴args)↓Command URL Params Headers Cert SSLFlags Priority
    ∇

    ∇ r←Run
      :Access public
      :If 0∊⍴Cert
          r←(Command HTTPCmd)URL Params Headers
      :Else
          r←(Cert SSLFlags Priority)(Command HTTPCmd)URL Params Headers
      :EndIf
    ∇

    ∇ r←Get args
      :Access public shared
      r←('GET'HTTPCmd)args
    ∇

    ∇ r←{certs}Do args
      :Access public shared
    ⍝ args is Command URL Params Headers Cert SSLFlags Priority
      args←eis args
      :If 0=⎕NC'certs'
          r←((⊃args)HTTPCmd)1↓args
      :Else
          r←certs((⊃args)HTTPCmd)1↓args
      :EndIf
    ∇

    ∇ r←{certs}(cmd HTTPCmd)args;url;parms;hdrs;urlparms;p;b;secure;port;host;page;x509;flags;priority;pars;auth;req;err;chunked;chunk;buffer;chunklength;done;data;datalen;header;headerlen;status;httpver;httpstatus;httpstatusmsg;rc;dyalog;FileSep;donetime;congaCopied;peercert
⍝ issue an HTTP command
⍝ certs - optional [X509Cert [SSLValidation [Priority]]]
⍝ args  - [1] URL in format [HTTP[S]://][user:pass@]url[:port][/page]
⍝         {2} parameters is using POST - either a namespace or URL-encoded string
⍝         {3} HTTP headers in form {↑}(('hdr1' 'val1')('hdr2' 'val2'))
⍝ Makes secure connection if left arg provided or URL begins with https:

⍝ Result: (conga return code) (HTTP Status) (HTTP headers) (HTTP body) [PeerCert if secure]
      r←⎕NS''
      (rc httpver httpstatus httpstatusmsg header data peercert)←¯1 '' 400(⊂'bad request')(0 2⍴⊂'')''⍬

      congaCopied←0
      :If 0∊⍴LocalDRC
          :Select ⊃#.⎕NC'DRC'
          :Case 9
              LDRC←#.DRC
          :Case 0
              FileSep←'/\'[1+'Win'≡3↑1⊃#.⎕WG'APLVersion']
              dyalog←{⍵,(-FileSep=¯1↑⍵)↓FileSep}2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'
              'DRC'⎕CY dyalog,'ws/conga' ⍝ runtime needs full workspace path
              :If {0::1 ⋄ 0⊣⍎'DRC'}''
                  ⎕←'Conga namespace DRC not found or defined'
                  →0
              :EndIf
              LDRC←DRC
              congaCopied←1
          :Else
              ⎕←'Conga namespace DRC not found or defined'
              →0
          :EndSelect
      :ElseIf 9=⎕NC'LocalDRC'
          LDRC←LocalDRC
      :Else
          LDRC←⍎⍕LocalDRC
      :EndIf

      {}LDRC.Init''
      args←eis args

      (url parms hdrs)←args,(⍴args)↓''(⎕NS'')''
      urlparms←''
      cmd←uc,cmd

      :If 'GET'≡cmd
          urlparms←parms
          parms←''
      :EndIf

      parms←URLEncode parms
      urlparms←{0∊⍴⍵:'' ⋄ ('?'=1↑⍵)↓'?',⍵}URLEncode urlparms

     GET:
      p←(∨/b)×1+(b←'//'⍷url)⍳1
      secure←{6::⍵ ⋄ ⍵∨0<⍴,certs}(lc(p-2)↑url)≡'https:'
      port←(1+secure)⊃80 443 ⍝ Default HTTP/HTTPS port
      url←p↓url              ⍝ Remove HTTP[s]:// if present
      (host page)←'/'split url,(~'/'∊url)/'/'    ⍝ Extract host and page from url

      :If 0=⎕NC'certs' ⋄ certs←'' ⋄ :EndIf

      :If secure
          x509 flags priority←3↑certs,(⍴,certs)↓(⎕NEW LDRC.X509Cert)32 'NORMAL:!CTYPE-OPENPGP'  ⍝ 32=Do not validate Certs
          pars←('x509'x509)('SSLValidation'flags)('Priority'priority)
      :Else ⋄ pars←''
      :EndIf

      :If '@'∊host ⍝ Handle user:password@host...
          auth←'Authorization: Basic ',(b64Encode(¯1+p←host⍳'@')↑host),NL
          host←p↓host
      :Else ⋄ auth←''
      :EndIf

      (host port)←port{(⍴⍵)<ind←⍵⍳':':⍵ ⍺ ⋄ (⍵↑⍨ind-1)(1⊃2⊃⎕VFI ind↓⍵)}host ⍝ Check for override of port number

      hdrs←makeHeaders hdrs
      hdrs←'User-Agent'(hdrs addHeader)'Dyalog/Conga'
      hdrs←'Accept'(hdrs addHeader)'*/*'

      :If ~0∊⍴parms
          :If cmd≡'POST'
              hdrs←'Content-Length'(hdrs addHeader)⍴parms
              hdrs←'Content-Type'(hdrs addHeader)'application/x-www-form-urlencoded'
          :EndIf
      :EndIf

      hdrs←'Accept-Encoding'(hdrs addHeader)'gzip, deflate'

      req←(uc cmd),' ',(page,urlparms),' HTTP/1.1',NL,'Host: ',host,NL
      req,←fmtHeaders hdrs
      req,←auth

      donetime←⌊⎕AI[3]+1000×WaitTime ⍝ time after which we'll time out

      :If 0=⊃(err cmd)←2↑rc←LDRC.Clt''host port'Text' 100000,pars ⍝ 100,000 is max receive buffer size
      :AndIf 0=⊃rc←LDRC.Send cmd(req,NL,parms)

          chunked chunk buffer chunklength←0 '' '' 0
          done data datalen headerlen header←0 ⍬ 0 0 ⍬
          :Repeat
              :If ~done←0≠1⊃rc←LDRC.Wait cmd 5000            ⍝ Wait up to 5 secs
                  :If rc[3]∊'Block' 'BlockLast'             ⍝ If we got some data
                      :If chunked
                          chunk←4⊃rc
                      :ElseIf 0<⍴data,←4⊃rc
                      :AndIf 0=headerlen
                          (headerlen header)←DecodeHeader data
                          :If 0<headerlen
                              data←headerlen↓data
                              :If chunked←∨/'chunked'⍷header getHeader'Transfer-Encoding'
                                  chunk←data
                                  data←''
                              :Else
                                  datalen←⊃(toNum header getHeader'Content-Length'),¯1 ⍝ ¯1 if no content length not specified
                              :EndIf
                          :EndIf
                      :EndIf
                  :Else
                      ⎕←rc ⍝ Error?
                      ∘∘∘  ⍝ !! Intentional !!
                  :EndIf
                  :If chunked
                      buffer,←chunk
                      :While done<¯1≠⊃(len chunklength)←getchunklen buffer
                          :If (⍴buffer)≥4+len+chunklength
                              data,←chunklength↑(len+2)↓buffer
                              buffer←(chunklength+len+4)↓buffer
                              :If done←0=chunklength ⍝ chunked transfer can add headers at the end of the transmission
                                  header←header⍪2⊃DecodeHeader buffer
                              :EndIf
                          :EndIf
                      :EndWhile
                  :Else
                      done←done∨'BlockLast'≡3⊃rc                        ⍝ Done if socket was closed
                      :If datalen>0
                          done←done∨datalen≤⍴data ⍝ ... or if declared amount of data rcvd
                      :Else
                          done←done∨(∨/''⍷data)∨(∨/''⍷data)
                      :EndIf
                  :EndIf
              :ElseIf 100=1⊃rc ⍝ timeout?
                  done←⎕AI[3]>donetime
              :EndIf
          :Until done

          :If 0=1⊃rc
              :Trap 0 ⍝ If any errors occur, abandon conversion
                  :Select header getHeader'content-encoding' ⍝ was the response compressed?
                  :Case 'deflate'
                      data←fromutf8 LDRC.flate.Inflate 120 156{(2×⍺≡2↑⍵)↓⍺,⍵}256|83 ⎕DR data ⍝ append 120 156 signature because web servers strip it out due to IE
                  :Case 'gzip'
                      data←fromutf8 256|¯3(219⌶)83 ⎕DR data
                  :Else
                      :If ∨/'charset=utf-8'⍷header getHeader'content-type'
                          data←'UTF-8'⎕UCS ⎕UCS data ⍝ Convert from UTF-8
                      :EndIf
                  :EndSelect

                  :If {(⍵[3]∊'12357')∧'30 '≡⍵[1 2 4]}4↑{⍵↓⍨⍵⍳' '}(⊂1 1)⊃header ⍝ redirected? (HTTP status codes 301, 302, 303, 305, 307)
                      →GET⍴⍨0<⍴url←'location'{(⍵[;1]⍳⊂⍺)⊃⍵[;2],⊂''}header ⍝ use the "location" header field for the URL
                  :EndIf

              :EndTrap

              httpver httpstatus httpstatusmsg←{⎕ML←3 ⋄ ⍵⊂⍨{⍵∨2<+\~⍵}⍵≠' '}(⊂1 1)⊃header
              header↓⍨←1

              :If secure ⋄ peercert←⊂LDRC.GetProp cmd'PeerCert' ⋄ :EndIf
          :EndIf

          r.(rc httpver httpstatus httpstatusmsg headers data peercert)←(1⊃rc)httpver(toNum httpstatus)httpstatusmsg header data peercert

      :Else
          ⎕←'Connection failed ',,⍕rc
          r.rc←1⊃rc
      :EndIf

      {}LDRC.Close cmd

      :If congaCopied
          {}LDRC.Close'.'
          LDRC.(⎕EX¨⍙naedfns)
      :EndIf
    ∇

    NL←⎕UCS 13 10
    fromutf8←{0::(⎕AV,'?')[⎕AVU⍳⍵] ⋄ 'UTF-8'⎕UCS ⍵} ⍝ Turn raw UTF-8 input into text
    utf8←{3=10|⎕DR ⍵: 256|⍵ ⋄ 'UTF-8' ⎕UCS ⍵}
    sint←{⎕io←0 ⋄ 83=⎕DR ⍵:⍵ ⋄ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 ¯128 ¯127 ¯126 ¯125 ¯124 ¯123 ¯122 ¯121 ¯120 ¯119 ¯118 ¯117 ¯116 ¯115 ¯114 ¯113 ¯112 ¯111 ¯110 ¯109 ¯108 ¯107 ¯106 ¯105 ¯104 ¯103 ¯102 ¯101 ¯100 ¯99 ¯98 ¯97 ¯96 ¯95 ¯94 ¯93 ¯92 ¯91 ¯90 ¯89 ¯88 ¯87 ¯86 ¯85 ¯84 ¯83 ¯82 ¯81 ¯80 ¯79 ¯78 ¯77 ¯76 ¯75 ¯74 ¯73 ¯72 ¯71 ¯70 ¯69 ¯68 ¯67 ¯66 ¯65 ¯64 ¯63 ¯62 ¯61 ¯60 ¯59 ¯58 ¯57 ¯56 ¯55 ¯54 ¯53 ¯52 ¯51 ¯50 ¯49 ¯48 ¯47 ¯46 ¯45 ¯44 ¯43 ¯42 ¯41 ¯40 ¯39 ¯38 ¯37 ¯36 ¯35 ¯34 ¯33 ¯32 ¯31 ¯30 ¯29 ¯28 ¯27 ¯26 ¯25 ¯24 ¯23 ¯22 ¯21 ¯20 ¯19 ¯18 ¯17 ¯16 ¯15 ¯14 ¯13 ¯12 ¯11 ¯10 ¯9 ¯8 ¯7 ¯6 ¯5 ¯4 ¯3 ¯2 ¯1[utf8 ⍵]}
    lc←(819⌶) ⍝ lower case conversion
    uc←1∘lc
    dlb←{(+/∧\' '=⍵)↓⍵} ⍝ delete leading blanks
    split←{(p↑⍵)((p←¯1+⍵⍳⍺)↓⍵)} ⍝ split ⍵ on first occurrence of ⍺
    h2d←{⎕IO←0 ⋄ 16⊥'0123456789abcdef'⍳lc ⍵} ⍝ hex to decimal
    getchunklen←{¯1=len←¯1+⊃(NL⍷⍵)/⍳⍴⍵:¯1 ¯1 ⋄ chunklen←h2d len↑⍵ ⋄ (⍴⍵)<len+chunklen+4:¯1 ¯1 ⋄ len chunklen}
    eis←{⍺←1 ⋄ ,(⊂⍣(⍺=|≡⍵))⍵} ⍝ enclose if simple
    toNum←{0∊⍴⍵:⍬ ⋄ 1⊃2⊃⎕VFI ⍕⍵}
    getHeader←{(⍺[;2],⊂'∘↑∘')⊃⍨(lc ¨⍺[;1])⍳eis lc ⍵}
    addHeader←{'∘↑∘'≡⍺⍺ getHeader ⍺:⍺⍺⍪⍺ ⍵ ⋄ ⍺⍺} ⍝ add a header unless it's already defined
    makeHeaders←{⎕ML←1 ⋄ 0∊⍴⍵:0 2⍴⊂'' ⋄ 2=⍴⍴⍵:⍵ ⋄ ↑2 eis ⍵}
    fmtHeaders←{⎕ML←1 ⋄ 0∊⍴⍵:'' ⋄ ∊{0∊⍴2⊃⍵:'' ⋄ NL,⍨(firstCaps 1⊃⍵),': ',⍕2⊃⍵}¨↓⍵}
    firstCaps←{1↓{(¯1↓0,'-'=⍵) (819⌶)¨ ⍵}'-',⍵}
      b64Encode←{ ⍝ Base64 Encode
          raw←⊃,/11∘⎕DR¨⍵
          cols←6
          rows←⌈(⊃⍴raw)÷cols
          mat←rows cols⍴(rows×cols)↑raw
          'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[⎕IO+2⊥⍉mat],(4|-rows)⍴'='}

    ∇ r←DecodeHeader buf;len;d;i
      ⍝ Decode HTTP Header
      r←0(0 2⍴⊂'')
      :If 0<i←⊃{((NL,NL)⍷⍵)/⍳⍴⍵}buf
          len←(¯1+⍴NL,NL)+i
          d←(⍴NL)↓¨{(NL⍷⍵)⊂⍵}NL,len↑buf
          d←↑{((p-1)↑⍵)((p←⍵⍳':')↓⍵)}¨d
          d[;1]←lc¨d[;1]
          d[;2]←dlb¨d[;2]
          r←len d
      :EndIf
    ∇

    ∇ r←{name}URLEncode data;⎕IO;z;ok;nul;m;enlist;noname
      :Access Public Shared
      noname←0
      :If 9.1=⎕NC⊂'data'
          data←{0∊⍴t←⍵.⎕NL ¯2:'' ⋄ ↑⍵{⍵(⍕,⍺⍎⍵)}¨t}data
      :Else
          :If 1≥|≡data
              :If noname←0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
              data←name data
          :EndIf
      :EndIf
      nul←⎕UCS ⎕IO←0
      enlist←{⎕ML←1 ⋄ ∊⍵}
      ok←nul,enlist ⎕UCS¨(⎕UCS'aA0')+⍳¨26 26 10

      z←⎕UCS'UTF-8'⎕UCS enlist nul,¨,data
      :If ∨/m←~z∊ok
          (m/z)←↓'%',(⎕D,⎕A)[⍉16 16⊤⎕UCS m/z]
          data←(⍴data)⍴1↓¨{(⍵=nul)⊂⍵}enlist z
      :EndIf

      r←noname↓¯1↓enlist data,¨(⍴data)⍴'=&'
    ∇

:EndClass


issue #67: support ⊃⍵ when 1<⍴⍴⍵

issue #67: support ⊃⍵ when 1<⍴⍴⍵


Update SQL.dyalog

Update SQL.dyalog

Connection warnings imply that conn was established, so the result of ConnectTo should show that (and not indicate failure as before)

: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
                  :If 1>1⊃rc←#.SQA.Connect conx dsn pwd user('DriverOptions'(opts)) ⋄ r←0 conx
                 ⍝ Baas: warning (¯1) means conn was established, so return ok!
                 ⍝ discussed with BHC in Italy ;-)
                  :Else ⋄ r←601 ''('Unable to connect to "',database,'" due to ',⍕3⊃rc)
                  :EndIf
                  :Else ⋄ r←601 ''('Unable to connect to "',database,'" due to ',⍕3⊃rc)
                  :EndIf
              :EndIf
          :EndIf
      :EndIf
    ∇

    ∇ r←Do ctl;c;m;z;ns;conx;data
  ⍝ Prepare, Execute, Fetch a statement.
  ⍝       ctl: database sqlstmt {bindvars} {nulls}
      r←⎕NS''
      r.(ReturnCode Data Message)←¯1(0 0⍴0)''
      →(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
      data←''
      :If 3≤⍴ctl
          :If 1<|≡3⊃ctl ⋄ data←3⊃ctl
              :If 3<⍴ctl ⋄ data←(⊂data),ctl[4] ⋄ :EndIf  ⍝ deal with nulls
          :Else ⋄ data←2↓ctl ⋄ :EndIf
      :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.Data←⊃2⊃z
     l2:
      :If 0<r.ReturnCode←1⊃z ⋄ r.Message←3⊃z ⋄ :EndIf
      →(0<1⊃z←#.SQA.Close conx)↓0
     l3:
      r.ReturnCode←1⊃z
      r.Message←3⊃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,'" due to ',⍕3⊃rc)
                  :EndIf
              :EndIf
          :EndIf
      :EndIf
    ∇

    ∇ r←Do ctl;c;m;z;ns;conx;data
  ⍝ Prepare, Execute, Fetch a statement.
  ⍝       ctl: database sqlstmt {bindvars} {nulls}
      r←⎕NS''
      r.(ReturnCode Data Message)←¯1(0 0⍴0)''
      →(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
      data←''
      :If 3≤⍴ctl
          :If 1<|≡3⊃ctl ⋄ data←3⊃ctl
              :If 3<⍴ctl ⋄ data←(⊂data),ctl[4] ⋄ :EndIf  ⍝ deal with nulls
          :Else ⋄ data←2↓ctl ⋄ :EndIf
      :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.Data←⊃2⊃z
     l2:
      :If 0<r.ReturnCode←1⊃z ⋄ r.Message←3⊃z ⋄ :EndIf
      →(0<1⊃z←#.SQA.Close conx)↓0
     l3:
      r.ReturnCode←1⊃z
      r.Message←3⊃z
    ∇

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

:EndNamespace


Remove bad quotes. Use one-liner transformation function.

 output←regexes(op Run args)input;from;to;options;lines;trans;Expand;repeat;s;r;last;fns;Quote;q;hasrepeat;postproc;combine

 :Trap 11 22 ⍝ accept filename or actual input
     input←input ⎕NTIE 0
 :EndTrap

 :Trap 11 22 ⍝ accept filename or actual regex
     regexes←⊃⎕NGET regexes 1
 :EndTrap

 repeat←⍎{×≢⍵:⍵ ⋄ '1'}args∩'≡',⎕D ⍝ extract repetition, if any, else 1
 hasrepeat←∨/args∊⍨'≡',⎕D
 ⍝ select options:
 options←('ResultText' 'Simple')('Greedy' 0)('IC' 1)('Mode' 'D')('Mode' 'M')('DotAll' 1)('UCP' 1)('OM' 1)/⍨1,'gidmauo'∊819⌶args~' -'
 s r←(∨/∊∘op)¨'Ss' 'Rr'
 options↓⍨←s ⍝ ResultText is not for ⎕S

 fns←+/∧\'⍵'∊¨regexes
 postproc←∊'{⍵}','∘{'∘,¨,∘'}'¨fns↑regexes ⍝ post-processing functions
 regexes↓⍨←fns

 last←⊃⌽regexes
 :If 2|≢regexes ⍝ odd number of strings → many-to-one
 :OrIf '⍵'∊last ⍝ transformation function → many-to-function
     from←¯1↓regexes ⍝ remove transformation pattern/function
     :If ~'⍵'∊last ⍝ no ⍵ → pattern
         Expand←'⍵B' '⍵b' '⍵P' '⍵p' '⍵M' '⍵O' '⍵L' '⍵N'⎕R'⍵.Block' '⍵.BlockNum' '⍵.Pattern' '⍵.PatternNum' '⍵.Match' '⍵.Offsets' '⍵.Lengths' '⍵.Names'
     :OrIf ~≡⎕FX,⊂'to←{',(r/',⍕{'),(Expand last),(r/'}⍵'),'}' ⍝ if definition fails, revert to pattern (allows ⍵ in pattern)
         to←⊃⌽regexes
     :EndIf
 :Else
     lines←2÷⍨≢regexes
     from←lines↑regexes
     to←lines↓regexes
 :EndIf

 combine←'{⊃,/⍵↑¨⍨⌈/≢¨⍵}'
 q←⎕NS ⍬
 :If s r∨.∧'?'∊args
     Quote←{q,q,⍨⍵/⍨1+⍵=q←''''}
     q.from←⍕Quote¨from
     :If 3=⎕NC'to'
         q.to←1⌽'  ',3↓∊⎕NR'to'
     :Else
         q.to←⍕Quote¨to
     :EndIf
     :If 3=⎕NC'repeat'
         q.repeat←'⍣',∊⎕NR'repeat'
     :ElseIf 1≢repeat
         q.repeat←'⍣',⍕repeat
     :Else
         q.repeat←''
     :EndIf
     :If ×≢options
         q.options←'⍠',∊{' (',(Quote⊃⍵),' ',({0 1∊⍨⊂⍵:⍕⍵ ⋄ '''',⍵,''''}⊃⌽⍵),')'}¨options
     :Else
         q.options←''
     :EndIf
     q.op←s r/'SR'
     q.postproc←4↓postproc
     q.postproc,←(hasrepeat∧s)/(''≡q.postproc)↓'∘',combine
     output←q.(postproc,from,'⎕',op,to,options,repeat)
 :Else
     :Trap 6 11
         :If r
             output←from ⎕R to⍠options⍣repeat⊢input
         :ElseIf s
             output←from ⎕S to⍠options⍣repeat⊢input
             output←(⍎combine)⍣hasrepeat⊢output ⍝ merge search results if we need to continue
         :EndIf
         output←(⍎postproc)output
     :Case 6 ⍝ no output
         output←'*** Left operand must be ''R'' or ''S'' ***'
     :Case 11 ⍝ faulty regex
         output←'*** ',⎕DMX.Message,' ─ try adding ? to the Arguments to see the attempted APL expression ***'
     :EndTrap
 :EndIf

 output←regexes(op Run args)input;from;to;options;lines;trans;Expand;repeat;s;r;last;fns;Quote;q;hasrepeat;postproc;combine

 :Trap 11 22 ⍝ accept filename or actual input
     input←input ⎕NTIE 0
 :EndTrap

 :Trap 11 22 ⍝ accept filename or actual regex
     regexes←⊃⎕NGET regexes 1
 :EndTrap

 repeat←⍎{×≢⍵:⍵ ⋄ '1'}args∩'≡',⎕D ⍝ extract repetition, if any, else 1
 hasrepeat←∨/args∊⍨'≡',⎕D
 ⍝ select options:
 options←('ResultText' 'Simple')('Greedy' 0)('IC' 1)('Mode' 'D')('Mode' 'M')('DotAll' '1')('UCP' 1)('OM' 1)/⍨1,'gidmauo'∊819⌶args~' -'
 s r←(∨/∊∘op)¨'Ss' 'Rr'
 options↓⍨←s ⍝ ResultText is not for ⎕S

 fns←+/∧\'⍵'∊¨regexes
 postproc←∊'{⍵}','∘{'∘,¨,∘'}'¨fns↑regexes ⍝ post-processing functions
 regexes↓⍨←fns

 last←⊃⌽regexes
 :If 2|≢regexes ⍝ odd number of strings → many-to-one
 :OrIf '⍵'∊last ⍝ transformation function → many-to-function
     from←¯1↓regexes ⍝ remove transformation pattern/function
     :If ~'⍵'∊last ⍝ no ⍵ → pattern
         Expand←'⍵B' '⍵b' '⍵P' '⍵p' '⍵M' '⍵O' '⍵L' '⍵N'⎕R'⍵.Block' '⍵.BlockNum' '⍵.Pattern' '⍵.PatternNum' '⍵.Match' '⍵.Offsets' '⍵.Lengths' '⍵.Names'
     :OrIf ~≡⎕FX'to←{'(r/',⍕{')(Expand last)(r/'}⍵')'}' ⍝ if definition fails, revert to pattern (allows ⍵ in pattern)
         to←⊃⌽regexes
     :EndIf
 :Else
     lines←2÷⍨≢regexes
     from←lines↑regexes
     to←lines↓regexes
 :EndIf

 combine←'{⊃,/⍵↑¨⍨⌈/≢¨⍵}'
 q←⎕NS ⍬
 :If s r∨.∧'?'∊args
     Quote←{q,q,⍨⍵/⍨1+⍵=q←''''}
     q.from←⍕Quote¨from
     :If 3=⎕NC'to'
         q.to←1⌽'  ',3↓∊⎕NR'to'
     :Else
         q.to←⍕Quote¨to
     :EndIf
     :If 3=⎕NC'repeat'
         q.repeat←'⍣',∊⎕NR'repeat'
     :ElseIf 1≢repeat
         q.repeat←'⍣',⍕repeat
     :Else
         q.repeat←''
     :EndIf
     :If ×≢options
         q.options←'⍠',∊{' (',(Quote⊃⍵),' ',({0 1∊⍨⊂⍵:⍕⍵ ⋄ '''',⍵,''''}⊃⌽⍵),')'}¨options
     :Else
         q.options←''
     :EndIf
     q.op←s r/'SR'
     q.postproc←4↓postproc
     q.postproc,←(hasrepeat∧s)/(''≡q.postproc)↓'∘',combine
     output←q.(postproc,from,'⎕',op,to,options,repeat)
 :Else
     :Trap 6 11
         :If r
             output←from ⎕R to⍠options⍣repeat⊢input
         :ElseIf s
             output←from ⎕S to⍠options⍣repeat⊢input
             output←(⍎combine)⍣hasrepeat⊢output ⍝ merge search results if we need to continue
         :EndIf
         output←(⍎postproc)output
     :Case 6 ⍝ no output
         output←'*** Left operand must be ''R'' or ''S'' ***'
     :Case 11 ⍝ faulty regex
         output←'*** ',⎕DMX.Message,' ─ try adding ? to the Arguments to see the attempted APL expression ***'
     :EndTrap
 :EndIf


changed log4j to log4cxx

changed log4j to log4cxx

git-svn-id: a7c1976543b50a5b791fd6a475a0ba067ae951b4@308287 13f79535-47bb-0310-9956-ffa450edef68

/*
 * ============================================================================
 *                   The Apache Software License, Version 1.1
 * ============================================================================
 *
 *    Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modifica-
 * tion, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of  source code must  retain the above copyright  notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The end-user documentation included with the redistribution, if any, must
 *    include  the following  acknowledgment:  "This product includes  software
 *    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
 *    Alternately, this  acknowledgment may  appear in the software itself,  if
 *    and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "log4cxx" and  "Apache Software Foundation"  must not be used to
 *    endorse  or promote  products derived  from this  software without  prior
 *    written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products  derived from this software may not  be called "Apache", nor may
 *    "Apache" appear  in their name,  without prior written permission  of the
 *    Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 * APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 * DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 * ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 * (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software  consists of voluntary contributions made  by many individuals
 * on  behalf of the Apache Software  Foundation.  For more  information on the
 * Apache Software Foundation, please see <http://www.apache.org/>.
 *
 */

/*
 * ============================================================================
 *                   The Apache Software License, Version 1.1
 * ============================================================================
 *
 *    Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modifica-
 * tion, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of  source code must  retain the above copyright  notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The end-user documentation included with the redistribution, if any, must
 *    include  the following  acknowledgment:  "This product includes  software
 *    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
 *    Alternately, this  acknowledgment may  appear in the software itself,  if
 *    and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "log4j" and  "Apache Software Foundation"  must not be used to
 *    endorse  or promote  products derived  from this  software without  prior
 *    written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products  derived from this software may not  be called "Apache", nor may
 *    "Apache" appear  in their name,  without prior written permission  of the
 *    Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 * APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 * DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 * ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 * (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software  consists of voluntary contributions made  by many individuals
 * on  behalf of the Apache Software  Foundation.  For more  information on the
 * Apache Software Foundation, please see <http://www.apache.org/>.
 *
 */


fix _JQ.Handler to try and catch grabbing data from the event

:Namespace _JQ
    (⎕IO ⎕ML)←1

    :section Common Code

    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵ ⋄ '"',(('"' ⎕R '\\\0')⍕⍵),'"'}
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓∊{⍵,', '}¨eis ⍵}
    fmtValue←{'⍎'=⊃,⍵:⍵ ⋄ #.JSON.fromAPL ⍵}

    ∇ r←opt(sel Update jqfn)val
    ⍝ update an option for a widget
      r←#.JQ.Execute'$(',(fmtSelector sel),').',jqfn,'("option","',opt,'",',(fmtValue val),');'
    ∇
    :endsection

    :section Base Classes
    :class _jqObject : #.HtmlElement
        :field public Selector←''      ⍝ Selector to apply the JQuery function to
        :field public JavaScript←''    ⍝ additional JavaScript to run AFTER the jQuery function, can be function chain, separate code or both
        :field public PreJavaScript←'' ⍝ additional JavaScript to run BEFORE the jQuery function
        :field public Var←''           ⍝ JavaScript variable name for created object
        :field public JQueryFn←''      ⍝ JQuery function to apply
        :field public JQPars←''        ⍝ JQuery function parameters
        :field public Type←''          ⍝ Type specification that may precede JQPars
        :field public shared readonly _true←#.JSON.true     ⍝ same definition as in #.JSON
        :field public shared readonly _false←#.JSON.false   ⍝ same definition as in #.JSON
        :field public ScriptOptions←⍬  ⍝ determines how script will be rendered [1] wrap with <script>...</script>, [2] wrap with $(function(){...})

        ∇ Make0
          :Access public
          :Implements constructor
        ∇

        ∇ Make1 pars
          :Access public
          :Implements constructor
          pars←eis pars
          JQueryFn Selector JQPars JavaScript Var PreJavaScript Type←7↑pars,(⍴pars)↓'' '' '' '' '' '' ''
        ∇

        ∇ r←Render
          :Access public
          SetUse
          r←ScriptOptions #.JQ.JQueryfn JQueryFn Selector JQPars JavaScript Var PreJavaScript Type
        ∇

        ∇ r←isSelector str ⍝ checks if str is probably a jQuery selector
          :Access public shared
          r←∨/str∊'''"*[]:>+~()#.'
        ∇

        :section APLJax

        ∇ r←renderContent content;c
          r←''
          content←eis content
          :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
        ∇

        ∇ r←selector Replace content
          :Access public
          r←⊂('replace'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Append content
          :Access public
          r←⊂('append'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Prepend content
          :Access public
          r←⊂('prepend'selector)('data'(renderContent content))
        ∇

        ∇ r←Execute content
          :Access public
          r←⊂('execute'(renderContent content))
        ∇

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

    :endclass

    :class _jqWidget : _jqObject

        :field public Options←''    ⍝ options for the object to be created
        :field public ContainerTag←'div' ⍝ default container type
        :field public Container
        :field public InternalEvents←'' ⍝ list of events the widget "knows" about
        :field public BuildHTML←1       ⍝ if 0, we build any HTML infrastructure for the widget, otherwise, assume the user built it
        :field public WidgetDef←'event,ui' 'event'  'ui' '$(event.currentTarget)' '.val()'  ⍝ see _JQ.RenderHandlerCore for details

        ∇ r←{a}rand w;⎕RL
          :Access public
          ⎕RL←0
          :If 0=⎕NC'a' ⋄ r←?w
          :Else ⋄ r←a?w ⋄ :EndIf
        ∇

        ∇ Make
          :Access public
          Options←⎕NS''
          Container←⎕NEW #.HtmlElement
          :Implements constructor
        ∇

        ∇ r←Render;build;html;handlers;js;oldJavaScript
          :Access public
          r←html←js←''
          SetUse

          BuildHTML∧←0∊⍴Selector ⍝ if the user explicitly specifies a selector, assume he's built the content himself

          :If BuildHTML
              Container.(id name type style class title)←Container.(id name type style class title){UNDEF≡⍵:⍺ ⋄ UNDEF≢⍺:⍺ ⋄ ⍵}¨⎕THIS.(id name type style class title)
              :If Container.id≡UNDEF
                  :If Container.name≢UNDEF
                      Container.id←Container.name
                  :Else
                      Container.id←SetId
                  :EndIf
              :EndIf
              Selector←'#',Container.id
          :EndIf

          handlers←''
          :If ~0∊⍴Handlers
              handlers←';',⍨∊¯1↓¨Handlers.Render
          :EndIf

          oldJavaScript←JavaScript
          JavaScript,←handlers
          JQPars←Options
          js←⎕BASE.Render
          JavaScript←oldJavaScript

          :If BuildHTML≥0∊⍴Container.Content
              :Select ⊃Selector
              :Case '#' ⍝ id?
                  Container.id←1↓Selector
              :Case '.' ⍝ class?
                  Container.class←1↓Selector
              :EndSelect
              :If ContainerTag{⍵≡(⍴⍵)↑⍺}'input'
              :AndIf UNDEF≡Container.name
                  Container.name←('.#'∊⍨⊃Selector)↓Selector
              :EndIf
              Container.Tag←ContainerTag
              html←Container.Render
          :EndIf
          r←html,js
        ∇

        ∇ {handler}←On args
          :Access public
          ⍝ args - event callback clientData javascript
          handler←⎕BASE.On args
          handler.InternalEvents←InternalEvents
          handler.(WidgetDef←WidgetRef.WidgetDef)
        ∇

        ∇ {r}←{name}Set value
          :Access public
          →(0∊⍴value)⍴0
          :If 326≠⎕DR Options ⋄ Options←⎕NS'' ⋄ :EndIf
          :If 0=⎕NC'name'
              :Trap 11
                  (name value)←ParseValue value
              :Else
                  ⎕SIGNAL/⎕DMX.(EM EN)
              :EndTrap
          :EndIf
          name(Options SetOption)value
          r←⎕THIS
        ∇

        ∇ {r}←name SetIfNotSet value
          :Access public
          :If 0∊⍴GetOption name
              name Set value
          :EndIf
          r←⎕THIS
        ∇

        ∇ name(ref SetOption)value;set;parent;ind;newref;chunk;n;now;new;chunkroot;array;val;pos
          →(0∊⍴value)⍴0
          :If 1<|≡name ⍝ multiple names?
              value←(⊂⍣((⎕DR value)∊80 82))value
              name(ref SetOption)¨value
          :Else

              set←{⍺⍺⍎'(',⍺,')←⍵'}

              :If 0∊⍴parent←(-'.'⍳⍨⌽name)↓name
                  name(ref set)value ⍝ single name: assign directly (may be more than 1 name)
              :Else
                  ind←name⍳'.'
                  chunk←¯1↓ind↑name
                  (chunkroot pos)←2↑'[]'#.Utils.penclose chunk

                  :If array←'[]'≡¯2↑chunk ⍝ is it an array assignment?
                      pos←⍴val←eis value
                      :Select ⊃ref.⎕NC chunkroot
                      :Case 0
                          new←⎕NS¨pos⍴⊂⍬
                          chunkroot(ref set)new
                      :Case 2 ⍝ already exists
                          ('Invalid option specification - length error on "',chunkroot,'".')⎕SIGNAL 5/⍨~(⍴new←ref⍎chunkroot)∊1,pos
                      :Else
                          ('Invalid option specification - "',chunkroot,'" is not an array.')⎕SIGNAL 11
                      :EndSelect
                      new{(ind↓name)(⍺ SetOption)⍵}¨val

                  :Else
                      (chunkroot pos)←2↑'[]'#.Utils.penclose chunk
                      pos←2⊃⎕VFI pos
                      :Trap 3 6
                          newref←ref⍎chunk
                      :Case 3 ⍝ index error
                          n←⍴now←ref⍎chunkroot
                          new←⎕NS¨(pos-n)⍴⊂⍬
                          chunkroot(ref set)now,new
                          newref←ref⍎chunk
                      :Case 6 ⍝ value error
                          chunkroot ref.⎕NS''
                          :If ~0∊pos
                              new←⎕NS¨pos⍴⊂⍬
                              chunkroot(ref set)new
                          :EndIf
                          newref←ref⍎chunk
                      :EndTrap
                      (ind↓name)(newref SetOption)value
                  :EndIf
              :EndIf
          :EndIf
        ∇

        ∇ r←ParseValue value
          :Access shared public
          :If 2=⍴⍴value
          :AndIf 2=¯1↑⍴value
          :ElseIf 2<|≡value
          :AndIf ∧/(,2)∘≡∘⍴¨value
              value←↑value
          :ElseIf 0=2|⍴value
              value←((0.5×⍴value),2)⍴value
          :Else
              'Invalid option format'⎕SIGNAL 11
          :EndIf
          r←↓[1]value
        ∇

        ∇ r←GetOption names
          :Access public
          r←{6::'' ⋄ Options.⍎⍵}¨{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          :If 1=⍴r ⋄ r←⊃r ⋄ :EndIf ⍝ if single setting requested
        ∇

        ∇ r←name Update value;v
          :Access public
          r←name(Selector #._JQ.Update JQueryFn)value
          name Set value
        ∇
    :endclass

    :class _jqUIWidget : _jqWidget

        :field public Force←0

⍝        :field public WidgetSyntax ←⊂'event,ui' 'event'  'ui' '$(event.target)' '.val()' ⍝ see Handler class below

        ∇ make
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor
        ∇

        ∇ make1 pars
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor :base pars
        ∇

        ∇ {r}←opts RenderHandler handler
          :Access public
          r←opts ⎕BASE.RenderHandler(handler WidgetDef Force)
        ∇

    :endclass

    :class _jqUITabbedWidget : _jqUIWidget
        :field public Titles←''
        :field public Id←''
        :field public Tabs←''
        :field public Widget

        ∇ Make0
          :Access public
          :Implements constructor
          Widget←⎕NS''
          Widget._function←{}
        ∇

        ∇ Make1 pars;t
          :Access public
          :Implements constructor
          :Select |≡pars
          :Case 2
              :If 2=⍴pars ⍝ could be title/tab or title/title
                  :If (10|⎕DR t←2⊃pars)∊0 2 ⍝ if the second element is character
                  :AndIf 1=⍴⍴t ⍝ and it's a vector
                  :AndIf 20>⍴t ⍝ and it's relatively short
                      pars←,⊂pars
                  :Else
                      pars←#.HtmlElement.eis¨pars
                  :EndIf
              :EndIf
          :Case 0 1
              pars←#.HtmlElement.eis pars
          :EndSelect
          Titles Tabs Options JavaScript Var←5↑pars,(⍴pars)↓'' '' '' '' ''
          Widget←⎕NS''
          Widget._function←{}
        ∇

        ∇ r←Render;opts
          :Access public
          'Title and Contents lengths do not match'⎕SIGNAL((⍴Titles)≠⍴Tabs)/5
          opts←Options
          :If 2≤|≡Options ⋄ opts←#.JSON.toJQueryParameters Options ⋄ :EndIf
          r←Widget._function Id Titles(RenderTab¨Tabs)opts JavaScript Var
          r,←⎕BASE.Render
        ∇

        ∇ r←RenderTab tab;e;t
          :Access public
          :If 326=⎕DR tab
              r←''
              :For e :In tab
                  t←⊃e
                  :If isInstance t
                  :AndIf isHtmlElement t
                      r,←e.Render
                  :ElseIf isClass t
                  :AndIf isHtmlElement t
                      r,←(⎕NEW∘{2<⍴,⍵:(⊃⍵)(1↓⍵) ⋄ ⍵}eis e).Render
                  :Else
                      r,←e
                  :EndIf
              :EndFor
          :Else
              r←,⍕tab
          :EndIf
        ∇

    :endclass
    :endsection

⍝ --- Events ---
    :Section Events

    :Class Handler
        :Field public Selector←''        ⍝ CSS/jQuery selector to bind handler to
        :Field public Delegates←''       ⍝ See jQuery.On for information about delegates
        :Field public Events←''          ⍝ events to bind
        :Field public ClientData←''      ⍝ any additional client data to send to server
        :Field public Callback←1         ⍝ execute AJAX callback to server?  or the name of the server-side callback function
        :Field public JavaScript←''      ⍝ JavaScript to execute prior to server callback
        :Field public Page←''            ⍝ server URL to request for an AJAX callback
        :Field public jQueryWrap←1       ⍝ wrap handler in $(function(){...});
        :Field public ScriptWrap←1       ⍝ wrap handler in <script>...</script>
        :Field public WidgetDef←''       ⍝ widget definitions (e.g. jQuery or Syncfusion, others libraries may different)
                                         ⍝                                          jQuery            Syncfusion
                                         ⍝ [1] event handler syntax:                'event,ui'        'argument'
                                         ⍝ [2] syntax to access the event object:   'event'           'argument'
                                         ⍝ [3] syntax to access the object's model: 'ui'              'argument.model'
                                         ⍝ [4] syntax to access the widget itself:  '$(event.target)' 'this.element'
                                         ⍝ [5] syntax to access the value of an input widget - this default to '' but may overridden by individual widget
                                         ⍝     see ejSlider as an example
                                         ⍝
        :Field public ForceInternal←¯1   ⍝ indicates whether to "force" the event to be treated as a widget internal event
        :Field public WidgetRef←''       ⍝ ref to widget instance if this handler is on
        :Field public Hourglass←¯1       ⍝ indicates whether to display hourglass during callback execution
                                         ⍝ 1 - yes, 0 - no, ¯1 - only if calling back to APL

        :Field public Uses←'JQuery'
        :Field public _PageRef_←''

        quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵~'⍎' ⋄ '"',(('"' ⎕R '\\\0')⍵),'"'} ⍝ quote unless already quoted or begins with ⍎
        ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
        eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple

        ∇ Make0
          :Access public
          :Implements constructor
          CommonSetup
        ∇

        ∇ Make1 params
          :Access public
          :Implements constructor
          params←#.HtmlElement.eis params
          (Selector Events Callback ClientData Delegates JavaScript Page)←7↑params,(⍴params)↓7⍴⊂''
          :If #.HtmlPage #.HtmlElement.isInstance Page ⋄ Page←Page.Page ⋄ :EndIf ⍝ if request object passed
          CommonSetup
        ∇

        ∇ CommonSetup;c
          :Access public
          :Trap 0
              c←#.HtmlElement.context'_PageRef'
              :If ~0∊⍴c
                  (c⍎'_PageRef').Use Uses
                  _PageRef_←c._PageRef
              :EndIf
          :EndTrap
          WidgetDef←'event,ui' 'event' 'ui' '$(event.target)' '.val()'
        ∇

        ∇ r←Render;sel;syn_handler;syn_event;syn_model;syn_this;data;useajax;force;cd;selector;arg;verb;name;phrase;datasel;JQfn;jqfn;hg;removehg;dtype;success;status;ajax;widget;syn_value;delegates;v;events;selSelector
          :Access public
          r←''
          :If ~0∊⍴Events ⍝ skip if no events specified

              :If #.MiPage #.HtmlElement.isInstance Page
                  Page←Page._PageName
              :EndIf

              (selector delegates)←2↑(eis Selector),'' ''

              :If ~0∊⍴Delegates
                  delegates←Delegates
              :EndIf

              :If useajax←(,0)≢,Callback ⍝ callback=0 → don't make callback to server; =1 → use APLJax, =charvec → call ⍎charvec
              :AndIf (0∊⍴Page)>0∊⍴_PageRef_
                  Page←_PageRef_._PageName
              :EndIf

              force←0
              events←Events
              :If widget←#.HtmlElement.isWidget WidgetRef ⍝ is this a widget handler?
                  WidgetDef←WidgetRef.WidgetDef
                  :If ForceInternal=¯1
                      :If ','∊Events ⍝ multiple events?
                          events←','#.Utils.penclose Events~' '
                      :Else
                          events←,⊂Events
                      :EndIf
                      force←∧/events∊WidgetRef.InternalEvents
                  :Else
                      force←ForceInternal
                  :EndIf
              :EndIf

              :If 0∊⍴selector  ⍝ empty selector → use entire page
                  :If widget
                      selector←WidgetRef.Selector
                  :ElseIf #.HtmlElement #.HtmlElement.isInstance ##
                      :If #.MiPage #.HtmlElement.isInstance ##
                          selector←'⍎document'
                      :Else
                          selector←'#',##.id
                      :EndIf
                  :Else
                      selector←'⍎document'
                  :EndIf
              :EndIf

              (syn_handler syn_event syn_model syn_this syn_value)←5↑WidgetDef
              data←'_event: ',syn_event,'.type, '
              data,←'_what: function(arg){try{return arg.attr("id");}catch(e){alert("_what");return"";}}(',syn_this,'), '
              data,←'_value: function(arg){try{return arg',syn_value,';}catch(e){alert("_value");return"";}}(',syn_this,'), '
              data,←'_selector: ',(quote selector~'⍎'),', '
              data,←'_target: function(arg){try{return arg.target.id;}catch(e){alert("_target");return"";}}(',syn_event,'), '
              data,←'_currentTarget: function(arg){try{return arg.currentTarget.id;}catch(e){alert("_currentTarget");return"";}}(',syn_event,'), '

              :If #.HtmlElement.isString Callback ⍝ numeric Callback 1-call APLJax, 0-no callback to server
              :AndIf ~0∊⍴Callback
                  data,←'_callback: ',(quote Callback),', '
              :EndIf

              :If 0∊⍴ClientData ⍝ if you don't specify any clientdata, we serialize any forms on the page
                  data,←'_serialized: $("form").serialize(), '
              :EndIf

              data←¯2↓data

              :If ~0∊⍴ClientData
                  :Select ≡⊃ClientData
                  :CaseList 0 ⍝ ClientData is a simple vector
                      ClientData←,⊂2⍴⊂,ClientData ⍝ so name and verb are set to the same
                  :CaseList 1 ⍝ first element is a vector
                      ClientData←,⊂ClientData ⍝ enclose it
                  :EndSelect

                  :For cd :In ClientData
                      cd←#.HtmlElement.eis cd
                      (name verb arg sel)←4↑cd,(⍴cd)↓4⍴⊂''
                      :If (~0∊⍴name)∨verb≡'serialize'
                      :AndIf ~0∊⍴verb
                          jqfn←'' ⍝ jQuery function to call if this client data element refers to a widget

                          :If (⊂verb)∊'html' 'val'  ⍝ neither html or val take an argument
                          :AndIf 0∊⍴sel
                              sel←arg
                          :EndIf

⍝ build the selector for the data element
                          :If sel≡'' ⍝ it's for the current element/widget
                              :If #.HtmlElement.isWidget WidgetRef
                                  datasel←'$(',(quote WidgetRef.Selector),').'
                                  :If ~0∊⍴jqfn←WidgetRef.JQueryFn
                                      datasel,←WidgetRef.JQueryFn
                                  :EndIf
                              :Else
                                  datasel←syn_this,'.'
                              :EndIf
                          :ElseIf #.HtmlElement #.HtmlElement.isInstance sel ⍝ selector is reference to other element
                              :If #._JQ._jqObject #.HtmlElement.isInstance sel ⍝ is it a jQuery-based object?
                                  :If 0∊⍴selSelector←sel.Selector ⍝ no Selector?
                                      sel.SetId
                                      selSelector←'#',sel.id
                                  :EndIf
                                  datasel←'$(',(quote selSelector),').'
                                  :If ~0∊⍴jqfn←sel.JQueryFn
                                      datasel,←sel.JQueryFn
                                  :EndIf
                              :Else ⍝ not a jQuery-based object
                                  sel.SetId
                                  datasel←'$(',(quote'#',sel.id),').'
                              :EndIf
                          :Else ⍝ sel is not an HtmlElement
                              :Select |≡sel
                              :CaseList 0 1
                                  datasel←'$(',(quote sel),').'
                              :Case 2
                                  datasel←'$(',(quote(1⊃sel)),').',jqfn←2⊃sel
                              :EndSelect
                          :EndIf

⍝ now figure out what data to generate
                          phrase←''
                          :Select verb

                          :CaseList 'attr' 'css' 'is' 'prop'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'(',(quote arg),')'

                          :CaseList 'html' 'val'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'()'

                          :Case 'option' ⍝ jQueryUI and Syncfusion widgets
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',datasel,'("option"))'
                              :Else
                                  phrase←'APLstringify(',datasel,'("option",',(quote arg),'))'
                              :EndIf

                          :Case 'method' ⍝ jQueryUI and Syncfusion widgets
                              :If 0∊⍴arg
                                  phrase←'"no method information specified!"'
                              :Else
                                  phrase←datasel,'(',(quote arg),')'
                              :EndIf

                          :CaseList 'event' 'this' 'argument'
                              v←('event' 'argument' 'this'⍳⊂verb)⊃syn_event syn_event syn_this
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',v,')'
                              :Else
                                  phrase←v,'.',arg
                              :EndIf

                          :CaseList 'model' 'ui' ⍝ widgets only
                              :If ~0∊⍴jqfn
                                  datasel,←'().'
                                  :If 0∊⍴arg
                                      phrase←'APLstringify(',syn_model,')'
                                  :Else
                                      phrase←syn_model,'.',arg
                                  :EndIf
                              :EndIf

                          :Case 'eval'
                              sel←'' ⍝ ignore selector on eval
                              phrase←'eval(',(quote arg),')'

                          :Case 'js'
                              sel←''
                              phrase←arg

                          :Case 'string'
                              phrase←quote arg

                          :Case 'serialize'
                              :If 0∊⍴sel
                                  :If 0∊⍴arg
                                      sel←'form'
                                  :Else
                                      sel←arg
                                  :EndIf
                              :EndIf
                              phrase←'$(',(quote sel),').serialize()'
                              name,←'_serialized'
                          :Else
                              :If '⍎'=⊃verb
                                  sel←''
                                  phrase←1↓verb

                              :Else
                                  #.Boot.Log'Unknown event handler verb: "',verb,'"',{0::'' ⋄ ' on page ',##._PageRef._PageName}⍬
                                  phrase←quote phrase
                              :EndIf
                          :EndSelect

                          data,←',',name,': ',phrase
                      :EndIf
                  :EndFor
              :EndIf

              (hg removehg)←((1+Hourglass=¯1)⊃Hourglass useajax)∘{⍺:'document.body.style.cursor="',⍵,'";' ⋄ ''}¨'wait' 'default'

              dtype←'"json"'
              success←'success: function(obj){APLJaxReturn(obj);document.body.style.cursor="default";}'
              status←'statusCode:{ 408: function(){alert("Session timed out");',removehg,'}}'
              ajax←(JavaScript ine JavaScript,(';'=¯1↑JavaScript)↓';'),useajax/hg,'$.ajax({url: ',(quote Page),', cache: false, type: "POST", dataType: ',dtype,', data: {',data,'}, ',success,', ',status,'});'

              :If widget
                  :If force
                      (eis events)(WidgetRef.Options{⍺⍺⍎⍺,'←⍵'})¨⊂'function(',syn_handler,'){',ajax,'}'
                  :Else
                      r←'.on(',(quote Events),', function(event,ui){',ajax,'});'
                  :EndIf
              :Else
                  r←'$(',(quote selector),').on(',(quote Events),(delegates ine', ',quote delegates),', function(event,ui){',ajax,'});'
                  :If jQueryWrap ⋄ r←'$(function(){',r,'});' ⋄ :EndIf
                  :If ScriptWrap ⋄ r←#.HTMLInput.JS r ⋄ :EndIf
              :EndIf
          :EndIf
        ∇

    :endclass
    :endsection

:EndNamespace

:Namespace _JQ
    (⎕IO ⎕ML)←1

    :section Common Code

    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵ ⋄ '"',(('"' ⎕R '\\\0')⍕⍵),'"'}
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓∊{⍵,', '}¨eis ⍵}
    fmtValue←{'⍎'=⊃,⍵:⍵ ⋄ #.JSON.fromAPL ⍵}

    ∇ r←opt(sel Update jqfn)val
    ⍝ update an option for a widget
      r←#.JQ.Execute'$(',(fmtSelector sel),').',jqfn,'("option","',opt,'",',(fmtValue val),');'
    ∇
    :endsection

    :section Base Classes
    :class _jqObject : #.HtmlElement
        :field public Selector←''      ⍝ Selector to apply the JQuery function to
        :field public JavaScript←''    ⍝ additional JavaScript to run AFTER the jQuery function, can be function chain, separate code or both
        :field public PreJavaScript←'' ⍝ additional JavaScript to run BEFORE the jQuery function
        :field public Var←''           ⍝ JavaScript variable name for created object
        :field public JQueryFn←''      ⍝ JQuery function to apply
        :field public JQPars←''        ⍝ JQuery function parameters
        :field public Type←''          ⍝ Type specification that may precede JQPars
        :field public shared readonly _true←#.JSON.true     ⍝ same definition as in #.JSON
        :field public shared readonly _false←#.JSON.false   ⍝ same definition as in #.JSON
        :field public ScriptOptions←⍬  ⍝ determines how script will be rendered [1] wrap with <script>...</script>, [2] wrap with $(function(){...})

        ∇ Make0
          :Access public
          :Implements constructor
        ∇

        ∇ Make1 pars
          :Access public
          :Implements constructor
          pars←eis pars
          JQueryFn Selector JQPars JavaScript Var PreJavaScript Type←7↑pars,(⍴pars)↓'' '' '' '' '' '' ''
        ∇

        ∇ r←Render
          :Access public
          SetUse
          r←ScriptOptions #.JQ.JQueryfn JQueryFn Selector JQPars JavaScript Var PreJavaScript Type
        ∇

        ∇ r←isSelector str ⍝ checks if str is probably a jQuery selector
          :Access public shared
          r←∨/str∊'''"*[]:>+~()#.'
        ∇

        :section APLJax

        ∇ r←renderContent content;c
          r←''
          content←eis content
          :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
        ∇

        ∇ r←selector Replace content
          :Access public
          r←⊂('replace'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Append content
          :Access public
          r←⊂('append'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Prepend content
          :Access public
          r←⊂('prepend'selector)('data'(renderContent content))
        ∇

        ∇ r←Execute content
          :Access public
          r←⊂('execute'(renderContent content))
        ∇

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

    :endclass

    :class _jqWidget : _jqObject

        :field public Options←''    ⍝ options for the object to be created
        :field public ContainerTag←'div' ⍝ default container type
        :field public Container
        :field public InternalEvents←'' ⍝ list of events the widget "knows" about
        :field public BuildHTML←1       ⍝ if 0, we build any HTML infrastructure for the widget, otherwise, assume the user built it
        :field public WidgetDef←'event,ui' 'event'  'ui' '$(event.currentTarget)' '.val()'  ⍝ see _JQ.RenderHandlerCore for details

        ∇ r←{a}rand w;⎕RL
          :Access public
          ⎕RL←0
          :If 0=⎕NC'a' ⋄ r←?w
          :Else ⋄ r←a?w ⋄ :EndIf
        ∇

        ∇ Make
          :Access public
          Options←⎕NS''
          Container←⎕NEW #.HtmlElement
          :Implements constructor
        ∇

        ∇ r←Render;build;html;handlers;js;oldJavaScript
          :Access public
          r←html←js←''
          SetUse

          BuildHTML∧←0∊⍴Selector ⍝ if the user explicitly specifies a selector, assume he's built the content himself

          :If BuildHTML
              Container.(id name type style class title)←Container.(id name type style class title){UNDEF≡⍵:⍺ ⋄ UNDEF≢⍺:⍺ ⋄ ⍵}¨⎕THIS.(id name type style class title)
              :If Container.id≡UNDEF
                  :If Container.name≢UNDEF
                      Container.id←Container.name
                  :Else
                      Container.id←SetId
                  :EndIf
              :EndIf
              Selector←'#',Container.id
          :EndIf

          handlers←''
          :If ~0∊⍴Handlers
              handlers←';',⍨∊¯1↓¨Handlers.Render
          :EndIf

          oldJavaScript←JavaScript
          JavaScript,←handlers
          JQPars←Options
          js←⎕BASE.Render
          JavaScript←oldJavaScript

          :If BuildHTML≥0∊⍴Container.Content
              :Select ⊃Selector
              :Case '#' ⍝ id?
                  Container.id←1↓Selector
              :Case '.' ⍝ class?
                  Container.class←1↓Selector
              :EndSelect
              :If ContainerTag{⍵≡(⍴⍵)↑⍺}'input'
              :AndIf UNDEF≡Container.name
                  Container.name←('.#'∊⍨⊃Selector)↓Selector
              :EndIf
              Container.Tag←ContainerTag
              html←Container.Render
          :EndIf
          r←html,js
        ∇

        ∇ {handler}←On args
          :Access public
          ⍝ args - event callback clientData javascript
          handler←⎕BASE.On args
          handler.InternalEvents←InternalEvents
          handler.(WidgetDef←WidgetRef.WidgetDef)
        ∇

        ∇ {r}←{name}Set value
          :Access public
          →(0∊⍴value)⍴0
          :If 326≠⎕DR Options ⋄ Options←⎕NS'' ⋄ :EndIf
          :If 0=⎕NC'name'
              :Trap 11
                  (name value)←ParseValue value
              :Else
                  ⎕SIGNAL/⎕DMX.(EM EN)
              :EndTrap
          :EndIf
          name(Options SetOption)value
          r←⎕THIS
        ∇

        ∇ {r}←name SetIfNotSet value
          :Access public
          :If 0∊⍴GetOption name
              name Set value
          :EndIf
          r←⎕THIS
        ∇

        ∇ name(ref SetOption)value;set;parent;ind;newref;chunk;n;now;new;chunkroot;array;val;pos
          →(0∊⍴value)⍴0
          :If 1<|≡name ⍝ multiple names?
              value←(⊂⍣((⎕DR value)∊80 82))value
              name(ref SetOption)¨value
          :Else

              set←{⍺⍺⍎'(',⍺,')←⍵'}

              :If 0∊⍴parent←(-'.'⍳⍨⌽name)↓name
                  name(ref set)value ⍝ single name: assign directly (may be more than 1 name)
              :Else
                  ind←name⍳'.'
                  chunk←¯1↓ind↑name
                  (chunkroot pos)←2↑'[]'#.Utils.penclose chunk

                  :If array←'[]'≡¯2↑chunk ⍝ is it an array assignment?
                      pos←⍴val←eis value
                      :Select ⊃ref.⎕NC chunkroot
                      :Case 0
                          new←⎕NS¨pos⍴⊂⍬
                          chunkroot(ref set)new
                      :Case 2 ⍝ already exists
                          ('Invalid option specification - length error on "',chunkroot,'".')⎕SIGNAL 5/⍨~(⍴new←ref⍎chunkroot)∊1,pos
                      :Else
                          ('Invalid option specification - "',chunkroot,'" is not an array.')⎕SIGNAL 11
                      :EndSelect
                      new{(ind↓name)(⍺ SetOption)⍵}¨val

                  :Else
                      (chunkroot pos)←2↑'[]'#.Utils.penclose chunk
                      pos←2⊃⎕VFI pos
                      :Trap 3 6
                          newref←ref⍎chunk
                      :Case 3 ⍝ index error
                          n←⍴now←ref⍎chunkroot
                          new←⎕NS¨(pos-n)⍴⊂⍬
                          chunkroot(ref set)now,new
                          newref←ref⍎chunk
                      :Case 6 ⍝ value error
                          chunkroot ref.⎕NS''
                          :If ~0∊pos
                              new←⎕NS¨pos⍴⊂⍬
                              chunkroot(ref set)new
                          :EndIf
                          newref←ref⍎chunk
                      :EndTrap
                      (ind↓name)(newref SetOption)value
                  :EndIf
              :EndIf
          :EndIf
        ∇

        ∇ r←ParseValue value
          :Access shared public
          :If 2=⍴⍴value
          :AndIf 2=¯1↑⍴value
          :ElseIf 2<|≡value
          :AndIf ∧/(,2)∘≡∘⍴¨value
              value←↑value
          :ElseIf 0=2|⍴value
              value←((0.5×⍴value),2)⍴value
          :Else
              'Invalid option format'⎕SIGNAL 11
          :EndIf
          r←↓[1]value
        ∇

        ∇ r←GetOption names
          :Access public
          r←{6::'' ⋄ Options.⍎⍵}¨{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          :If 1=⍴r ⋄ r←⊃r ⋄ :EndIf ⍝ if single setting requested
        ∇

        ∇ r←name Update value;v
          :Access public
          r←name(Selector #._JQ.Update JQueryFn)value
          name Set value
        ∇
    :endclass

    :class _jqUIWidget : _jqWidget

        :field public Force←0

⍝        :field public WidgetSyntax ←⊂'event,ui' 'event'  'ui' '$(event.target)' '.val()' ⍝ see Handler class below

        ∇ make
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor
        ∇

        ∇ make1 pars
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor :base pars
        ∇

        ∇ {r}←opts RenderHandler handler
          :Access public
          r←opts ⎕BASE.RenderHandler(handler WidgetDef Force)
        ∇

    :endclass

    :class _jqUITabbedWidget : _jqUIWidget
        :field public Titles←''
        :field public Id←''
        :field public Tabs←''
        :field public Widget

        ∇ Make0
          :Access public
          :Implements constructor
          Widget←⎕NS''
          Widget._function←{}
        ∇

        ∇ Make1 pars;t
          :Access public
          :Implements constructor
          :Select |≡pars
          :Case 2
              :If 2=⍴pars ⍝ could be title/tab or title/title
                  :If (10|⎕DR t←2⊃pars)∊0 2 ⍝ if the second element is character
                  :AndIf 1=⍴⍴t ⍝ and it's a vector
                  :AndIf 20>⍴t ⍝ and it's relatively short
                      pars←,⊂pars
                  :Else
                      pars←#.HtmlElement.eis¨pars
                  :EndIf
              :EndIf
          :Case 0 1
              pars←#.HtmlElement.eis pars
          :EndSelect
          Titles Tabs Options JavaScript Var←5↑pars,(⍴pars)↓'' '' '' '' ''
          Widget←⎕NS''
          Widget._function←{}
        ∇

        ∇ r←Render;opts
          :Access public
          'Title and Contents lengths do not match'⎕SIGNAL((⍴Titles)≠⍴Tabs)/5
          opts←Options
          :If 2≤|≡Options ⋄ opts←#.JSON.toJQueryParameters Options ⋄ :EndIf
          r←Widget._function Id Titles(RenderTab¨Tabs)opts JavaScript Var
          r,←⎕BASE.Render
        ∇

        ∇ r←RenderTab tab;e;t
          :Access public
          :If 326=⎕DR tab
              r←''
              :For e :In tab
                  t←⊃e
                  :If isInstance t
                  :AndIf isHtmlElement t
                      r,←e.Render
                  :ElseIf isClass t
                  :AndIf isHtmlElement t
                      r,←(⎕NEW∘{2<⍴,⍵:(⊃⍵)(1↓⍵) ⋄ ⍵}eis e).Render
                  :Else
                      r,←e
                  :EndIf
              :EndFor
          :Else
              r←,⍕tab
          :EndIf
        ∇

    :endclass
    :endsection

⍝ --- Events ---
    :Section Events

    :Class Handler
        :Field public Selector←''        ⍝ CSS/jQuery selector to bind handler to
        :Field public Delegates←''       ⍝ See jQuery.On for information about delegates
        :Field public Events←''          ⍝ events to bind
        :Field public ClientData←''      ⍝ any additional client data to send to server
        :Field public Callback←1         ⍝ execute AJAX callback to server?  or the name of the server-side callback function
        :Field public JavaScript←''      ⍝ JavaScript to execute prior to server callback
        :Field public Page←''            ⍝ server URL to request for an AJAX callback
        :Field public jQueryWrap←1       ⍝ wrap handler in $(function(){...});
        :Field public ScriptWrap←1       ⍝ wrap handler in <script>...</script>
        :Field public WidgetDef←''       ⍝ widget definitions (e.g. jQuery or Syncfusion, others libraries may different)
                                         ⍝                                          jQuery            Syncfusion
                                         ⍝ [1] event handler syntax:                'event,ui'        'argument'
                                         ⍝ [2] syntax to access the event object:   'event'           'argument'
                                         ⍝ [3] syntax to access the object's model: 'ui'              'argument.model'
                                         ⍝ [4] syntax to access the widget itself:  '$(event.target)' 'this.element'
                                         ⍝ [5] syntax to access the value of an input widget - this default to '' but may overridden by individual widget
                                         ⍝     see ejSlider as an example
                                         ⍝
        :Field public ForceInternal←¯1   ⍝ indicates whether to "force" the event to be treated as a widget internal event
        :Field public WidgetRef←''       ⍝ ref to widget instance if this handler is on
        :Field public Hourglass←¯1       ⍝ indicates whether to display hourglass during callback execution
                                         ⍝ 1 - yes, 0 - no, ¯1 - only if calling back to APL

        :Field public Uses←'JQuery'
        :Field public _PageRef_←''

        quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵~'⍎' ⋄ '"',(('"' ⎕R '\\\0')⍵),'"'} ⍝ quote unless already quoted or begins with ⍎
        ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
        eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple

        ∇ Make0
          :Access public
          :Implements constructor
          CommonSetup
        ∇

        ∇ Make1 params
          :Access public
          :Implements constructor
          params←#.HtmlElement.eis params
          (Selector Events Callback ClientData Delegates JavaScript Page)←7↑params,(⍴params)↓7⍴⊂''
          :If #.HtmlPage #.HtmlElement.isInstance Page ⋄ Page←Page.Page ⋄ :EndIf ⍝ if request object passed
          CommonSetup
        ∇

        ∇ CommonSetup;c
          :Access public
          :Trap 0
              c←#.HtmlElement.context'_PageRef'
              :If ~0∊⍴c
                  (c⍎'_PageRef').Use Uses
                  _PageRef_←c._PageRef
              :EndIf
          :EndTrap
          WidgetDef←'event,ui' 'event' 'ui' '$(event.target)' '.val()'
        ∇

        ∇ r←Render;sel;syn_handler;syn_event;syn_model;syn_this;data;useajax;force;cd;selector;arg;verb;name;phrase;datasel;JQfn;jqfn;hg;removehg;dtype;success;status;ajax;widget;syn_value;delegates;v;events;try;selSelector
          :Access public
          r←''
          :If ~0∊⍴Events ⍝ skip if no events specified

              :If #.MiPage #.HtmlElement.isInstance Page
                  Page←Page._PageName
              :EndIf

              (selector delegates)←2↑(eis Selector),'' ''

              :If ~0∊⍴Delegates
                  delegates←Delegates
              :EndIf

              :If useajax←(,0)≢,Callback ⍝ callback=0 → don't make callback to server; =1 → use APLJax, =charvec → call ⍎charvec
              :AndIf (0∊⍴Page)>0∊⍴_PageRef_
                  Page←_PageRef_._PageName
              :EndIf

              force←0
              events←Events
              :If widget←#.HtmlElement.isWidget WidgetRef ⍝ is this a widget handler?
                  WidgetDef←WidgetRef.WidgetDef
                  :If ForceInternal=¯1
                      :If ','∊Events ⍝ multiple events?
                          events←','#.Utils.penclose Events~' '
                      :Else
                          events←,⊂Events
                      :EndIf
                      force←∧/events∊WidgetRef.InternalEvents
                  :Else
                      force←ForceInternal
                  :EndIf
              :EndIf

              :If 0∊⍴selector  ⍝ empty selector → use entire page
                  :If widget
                      selector←WidgetRef.Selector
                  :ElseIf #.HtmlElement #.HtmlElement.isInstance ##
                      :If #.MiPage #.HtmlElement.isInstance ##
                          selector←'⍎document'
                      :Else
                          selector←'#',##.id
                      :EndIf
                  :Else
                      selector←'⍎document'
                  :EndIf
              :EndIf

              (syn_handler syn_event syn_model syn_this syn_value)←5↑WidgetDef
              try←{'(function(){try{return ',⍵,';}catch(e){return "";}})()'}
              data←'_event: ',(try syn_event,'.type'),', '
              data,←'_what: ',(try syn_this,'.attr("id")'),', '
              data,←'_value: ',(try syn_this,syn_value),', '
              data,←'_selector: ',(quote selector~'⍎'),', '
              data,←'_target: ',(try syn_event,'.target.id'),', '
              data,←'_currentTarget: ',(try syn_event,'.currentTarget.id'),', '

              :If #.HtmlElement.isString Callback ⍝ numeric Callback 1-call APLJax, 0-no callback to server
              :AndIf ~0∊⍴Callback
                  data,←'_callback: ',(quote Callback),', '
              :EndIf

              :If 0∊⍴ClientData ⍝ if you don't specify any clientdata, we serialize any forms on the page
                  data,←'_serialized: $("form").serialize(), '
              :EndIf

              data←¯2↓data

              :If ~0∊⍴ClientData
                  :Select ≡⊃ClientData
                  :CaseList 0 ⍝ ClientData is a simple vector
                      ClientData←,⊂2⍴⊂,ClientData ⍝ so name and verb are set to the same
                  :CaseList 1 ⍝ first element is a vector
                      ClientData←,⊂ClientData ⍝ enclose it
                  :EndSelect

                  :For cd :In ClientData
                      cd←#.HtmlElement.eis cd
                      (name verb arg sel)←4↑cd,(⍴cd)↓4⍴⊂''
                      :If (~0∊⍴name)∨verb≡'serialize'
                      :AndIf ~0∊⍴verb
                          jqfn←'' ⍝ jQuery function to call if this client data element refers to a widget

                          :If (⊂verb)∊'html' 'val'  ⍝ neither html or val take an argument
                          :AndIf 0∊⍴sel
                              sel←arg
                          :EndIf

⍝ build the selector for the data element
                          :If sel≡'' ⍝ it's for the current element/widget
                              :If #.HtmlElement.isWidget WidgetRef
                                  datasel←'$(',(quote WidgetRef.Selector),').'
                                  :If ~0∊⍴jqfn←WidgetRef.JQueryFn
                                      datasel,←WidgetRef.JQueryFn
                                  :EndIf
                              :Else
                                  datasel←syn_this,'.'
                              :EndIf
                          :ElseIf #.HtmlElement #.HtmlElement.isInstance sel ⍝ selector is reference to other element
                              :If #._JQ._jqObject #.HtmlElement.isInstance sel ⍝ is it a jQuery-based object?
                                  :If 0∊⍴selSelector←sel.Selector ⍝ no Selector?
                                      sel.SetId
                                      selSelector←'#',sel.id
                                  :EndIf
                                  datasel←'$(',(quote selSelector),').'
                                  :If ~0∊⍴jqfn←sel.JQueryFn
                                      datasel,←sel.JQueryFn
                                  :EndIf
                              :Else ⍝ not a jQuery-based object
                                  sel.SetId
                                  datasel←'$(',(quote'#',sel.id),').'
                              :EndIf
                          :Else ⍝ sel is not an HtmlElement
                              :Select |≡sel
                              :CaseList 0 1
                                  datasel←'$(',(quote sel),').'
                              :Case 2
                                  datasel←'$(',(quote(1⊃sel)),').',jqfn←2⊃sel
                              :EndSelect
                          :EndIf

⍝ now figure out what data to generate
                          phrase←''
                          :Select verb

                          :CaseList 'attr' 'css' 'is' 'prop'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'(',(quote arg),')'

                          :CaseList 'html' 'val'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'()'

                          :Case 'option' ⍝ jQueryUI and Syncfusion widgets
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',datasel,'("option"))'
                              :Else
                                  phrase←'APLstringify(',datasel,'("option",',(quote arg),'))'
                              :EndIf

                          :Case 'method' ⍝ jQueryUI and Syncfusion widgets
                              :If 0∊⍴arg
                                  phrase←'"no method information specified!"'
                              :Else
                                  phrase←datasel,'(',(quote arg),')'
                              :EndIf

                          :CaseList 'event' 'this' 'argument'
                              v←('event' 'argument' 'this'⍳⊂verb)⊃syn_event syn_event syn_this
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',v,')'
                              :Else
                                  phrase←v,'.',arg
                              :EndIf

                          :CaseList 'model' 'ui' ⍝ widgets only
                              :If ~0∊⍴jqfn
                                  datasel,←'().'
                                  :If 0∊⍴arg
                                      phrase←'APLstringify(',syn_model,')'
                                  :Else
                                      phrase←syn_model,'.',arg
                                  :EndIf
                              :EndIf

                          :Case 'eval'
                              sel←'' ⍝ ignore selector on eval
                              phrase←'eval(',(quote arg),')'

                          :Case 'js'
                              sel←''
                              phrase←arg

                          :Case 'string'
                              phrase←quote arg

                          :Case 'serialize'
                              :If 0∊⍴sel
                                  :If 0∊⍴arg
                                      sel←'form'
                                  :Else
                                      sel←arg
                                  :EndIf
                              :EndIf
                              phrase←'$(',(quote sel),').serialize()'
                              name,←'_serialized'
                          :Else
                              :If '⍎'=⊃verb
                                  sel←''
                                  phrase←1↓verb

                              :Else
                                  #.Boot.Log'Unknown event handler verb: "',verb,'"',{0::'' ⋄ ' on page ',##._PageRef._PageName}⍬
                                  phrase←quote phrase
                              :EndIf
                          :EndSelect

                          data,←',',name,': ',phrase
                      :EndIf
                  :EndFor
              :EndIf

              (hg removehg)←((1+Hourglass=¯1)⊃Hourglass useajax)∘{⍺:'document.body.style.cursor="',⍵,'";' ⋄ ''}¨'wait' 'default'

              dtype←'"json"'
              success←'success: function(obj){APLJaxReturn(obj);document.body.style.cursor="default";}'
              status←'statusCode:{ 408: function(){alert("Session timed out");',removehg,'}}'
              ajax←(JavaScript ine JavaScript,(';'=¯1↑JavaScript)↓';'),useajax/hg,'$.ajax({url: ',(quote Page),', cache: false, type: "POST", dataType: ',dtype,', data: {',data,'}, ',success,', ',status,'});'

              :If widget
                  :If force
                      (eis events)(WidgetRef.Options{⍺⍺⍎⍺,'←⍵'})¨⊂'function(',syn_handler,'){',ajax,'}'
                  :Else
                      r←'.on(',(quote Events),', function(event,ui){',ajax,'});'
                  :EndIf
              :Else
                  r←'$(',(quote selector),').on(',(quote Events),(delegates ine', ',quote delegates),', function(event,ui){',ajax,'});'
                  :If jQueryWrap ⋄ r←'$(function(){',r,'});' ⋄ :EndIf
                  :If ScriptWrap ⋄ r←#.HTMLInput.JS r ⋄ :EndIf
              :EndIf
          :EndIf
        ∇

    :endclass
    :endsection

:EndNamespace


Shortened About text to avoid scroll bar...

:Class About_MiServer : MiPageSample
⍝ Control:: About_MiServer
⍝ Description:: Introduction to MiServer 3.0

    ∇ Compose;D
      :Access public

      Add _.p ScriptFollows
    ⍝  MiServer is a free, open-source web server implemented in Dyalog APL.
    ⍝  It enables the APL user to build sophisticated websites with little to no
    ⍝  knowledge of the underlying web technologies (HTML, JavaScript, CSS, etc).

      Add _.p ScriptFollows
    ⍝  This website serves as guide to MiServer 3.0 – providing documentation, samples,
    ⍝  and advice. At the same time, it showcases many of MiServer's capabilities,
    ⍝  as it is built almost entirely in APL using components available in MiServer.

      Add _.p ScriptFollows
    ⍝  Unfold the branches of the  Getting Started tree at the top left
    ⍝  to find documentation and some small demonstration applications. The
    ⍝  Controls tree contains lists of all the controls or widgets that
    ⍝  are built-in to MiServer 3.0, with examples of use.

      D←'src=/Examples/Data/Dyalog-D.png' 'alt="Dyalog-D"'
      D,←'height="20px"' 'style="vertical-align:bottom;"'
      D New←_.img
      (Add _.p ScriptFollows).Add¨D'in the top left corner.'
    ⍝  Tip: Some of the samples will be a little cramped when displayed in line
    ⍝  on this page. Look for the "pop out" link to view a sample
    ⍝  in a tab of its own.
    ⍝  When a sample is popped out into its own tab or window, the source
    ⍝  code and page content can be toggled by clicking the large Dyalog

      Add _.p ScriptFollows
    ⍝  We hope that you will enjoy MiServer; join us on the
    ⍝  forums (see the Resources drop-down at the top right), and contribute
    ⍝  samples or extensions via GitHub.

    ∇

:endclass

:class About_MiServer : MiPageSample
⍝ Control:: About_MiServer
⍝ Description:: Introduction to MiServer 3.0

    ∇ Compose;D
      :Access public

      Add _.p ScriptFollows
    ⍝  MiServer is a free, open-source web server implemented in Dyalog APL.
    ⍝  It enables the APL user to build sophisticated websites with little to no
    ⍝  knowledge of the underlying web technologies (HTML, JavaScript, CSS, etc).

      Add _.p ScriptFollows
    ⍝  This website serves as guide to MiServer 3.0 – providing documentation, samples,
    ⍝  and advice. At the same time, it showcases many of MiServer's capabilities,
    ⍝  as it is built almost entirely in APL using components available in MiServer.

      Add _.p ScriptFollows
    ⍝  Unfold the branches of the  Getting Started tree at the top left
    ⍝  to find documentation and some small demonstration applications. The
    ⍝  Controls tree contains lists of all the controls or widgets that
    ⍝  are built-in to MiServer 3.0, with examples of use.

      D←'src=/Examples/Data/Dyalog-D.png' 'alt="Dyalog-D"'
      D,←'height="20px"' 'style="vertical-align:bottom;"'
      D New←_.img
      (Add _.p ScriptFollows).Add¨D'in the top left corner.'
    ⍝  Tip: Some of the samples will be a little cramped when displayed in line
    ⍝  on this page. Look for the "pop out" link to view a sample
    ⍝  in a tab of its own.
    ⍝  When a sample is popped out into its own tab or window, the source
    ⍝  code and page content can be toggled by clicking the large Dyalog

      Add _.p ScriptFollows
    ⍝  We hope that you will enjoy using MiServer! Please use the
    ⍝  forums (see the Resources drop-down at the top right), and contribute
    ⍝  samples or extensions via GitHub.

    ∇

:endclass