MiServer.HandleMSP now checks if the in-workspace copy and source file match before overwriting page instance (thus preserving page content during debugging sessions)

:Class MiServer

    :Field Public Config

    :Field Public TID←¯1                ⍝ Thread ID Server is running under
    :Field Public BlockSize←640000      ⍝ Blocksize when returning files

    :Field Public SessionHandler←⎕NS ''
    :Field Public Authentication←⎕NS ''
    :Field Public Logger←⎕NS ''
    :Field Public Application←⎕NS ''

    :field Public Encoders←⍬  ⍝ pointers to instances of content encoders
    :field Public Datasources←⍬

    ⎕TRAP←0/⎕TRAP ⋄ (⎕ML ⎕IO)←1 1

    unicode←80=⎕DR 'A'
    NL←(CR LF)←⎕UCS 13 10
    FindFirst←{(⍺⍷⍵)⍳1}
    enlist←{⎕ML←1 ⋄ ∊⍵}
    fromutf8←{0::(⎕AV,'?')[⎕AVU⍳⍵] ⋄ 'UTF-8' ⎕UCS ⍵} ⍝ Turn raw UTF-8 input into text
    toutf8←{'UTF-8' ⎕UCS ⍵}                          ⍝ Turn text into UTF-8 byte stream
    setting←{0=⎕NC ⍵:⍺ ⋄ ⍎⍵}
    endswith←{(,⍺){⍵≡⍺↑⍨-⍴⍵},⍵}
    I85←{}

      bit←{⎕IO←0  ⍝ used by Log
          0=⍺:0 ⍝ all bits turned off
          ¯1=⍺:1 ⍝ all bits turned on
          (⌈2⍟⍵)⊃⌽((1+⍵)⍴2)⊤⍺}

    :section Override
⍝ ↓↓↓--- Methods which are usually overridden ---

    ∇ onServerStart
      :Access Public Overridable
    ⍝ Handle any server startup processing
    ∇

    ∇ onSessionStart req
      :Access Public Overridable
    ⍝ Process a new session
    ∇

    ∇ onSessionEnd session
      :Access Public Overridable
    ⍝ Handle the end of a session
    ∇

    ∇ onIdle
      :Access Public Overridable
    ⍝ Idle time handler - called when the server has gone idle for a period of time
    ∇

    ∇ Error req
      :Access Public Overridable
    ⍝ Handle trapped errors
      req.Response.HTML←'',(⊃,/⎕DM,¨⊂'
'),''
      req.Fail 500 ⍝ Internal Server Error
      1 Log ⎕DM
    ∇

    ∇ level Log msg
      :Access Public overridable
    ⍝ Logs server messages
    ⍝ levels implemented in MildServer are:
    ⍝ 1-error/important, 2-warning, 4-informational, 8-transaction (GET/POST)
      :If Config.LogMessageLevel bit level ⍝ if set to display this level of message
          ⎕←msg ⍝ display it
      :EndIf
    ∇

    ∇ Cleanup
      :Access Public overridable
    ⍝ Perform any site specific cleanup
    ∇

⍝ ↑↑↑--- End of Overridable methods ---
    :endsection

⍝ ↓↓↓--- Begin MildServer Core Code
    :section Start/Stop
    ∇ Run
      :Access Public
      ('Already Running on Thread',⍕TID)⎕SIGNAL(TID∊⎕TNUMS)/11
      TID←RunServer&⍬
    ∇

    ∇ End;tempfiles
    ⍝ Called by destructor
      :Access Public
      Logger.Stop ⍬
      :If 0=⎕NC'ServerName'
         ⍝!!! {}1 #.DRC.Init''
      :Else
          'Not running'⎕SIGNAL(#.DRC.Exists ServerName)↓11
          {}#.DRC.Close ServerName
          :Trap 6 ⍝ ⎕TSYNC may not return a value if the thread doesn't, so handle the possible VALUE ERROR
              {}⎕TSYNC ⎕TID~⍨TID∩⎕TNUMS
          :EndTrap
      :EndIf
      :If ~0∊⍴Config.TempFolder
          tempfiles←{(~⍵[;4])/⍵[;1]}#.Files.List Config.TempFolder
          :If 0≠⍴tempfiles←(tempfiles∨.≠¨'.')/tempfiles
              {}{0::'' ⋄ #.Files.Delete Config.TempFolder,⍵}¨tempfiles
          :EndIf
      :EndIf
      Cleanup ⍝ overridable
      TID←¯1
    ∇
    :endsection

    ∇ r←RunServer arg;RESUME;⎕TRAP;Common;cmd;name;port;wres;ref;nspc;sink;Stop;secure;certpath;flags;z;rc;rate;ErrorCount;idletime;msg;uid;ts;CMTid
      ⍝ Simple HTTP (Web) Server framework
      ⍝ Assumes Conga available in #.DRC and uses #.HTTPRequest
      ⍝ arg: dummy
      ⍝ certs: RootCertDir, ServerCert, ServerKey (optional: runs Secure server)

      Common←⎕NS'' ⋄ Stop←0 ⋄ WAITING←0
      CMTid←ConnectionMonitor&ServerName

      :If Config.TrapErrors>0
          ⎕TRAP←#.DrA.TrapServer
          #.DrA.NoUser←1+#.DrA.MailRecipient∨.≠' '
      :EndIf ⍝ Trap MiServer Errors. See HandleMSP for Page Errors.

      onServerStart ⍝ meant to be overridden

      idletime←#.Dates.DateToIDN ⎕TS
      :While ~Stop
          wres←#.DRC.Wait ServerName 20000 ⍝ Tick every 20 secs
          ⍝ wres: (return code) (object name) (command) (data)

          :Select 1⊃wres
          :Case 0 ⍝ Good data from RPC.Wait
              :Select 3⊃wres

              :Case 'Error'
                  :If ServerName≡2⊃wres
                      Stop←1
                  :EndIf
                  (1+(4⊃wres)∊1008 1105 1119)Log'RunServer: DRC.Wait reported error ',(⍕#.DRC.Error 4⊃wres),' on ',2⊃wres

                  ⎕EX SpaceName 2⊃wres

              :CaseList 'Block' 'BlockLast'
                  nspc←SpaceName 2⊃wres
                  :If 0=⎕NC nspc
                      :Trap 6 ⍝ VALUE ERROR - nspc could be erased if page goes away
                          nspc ⎕NS''
                          (⍎nspc).(Pos Buffer ContentLength Req)←0 '' 0 ''
                          :If 0=1⊃z←#.DRC.GetProp(2⊃wres)'PeerAddr' ⋄ (⍎nspc).PeerAddr←2⊃z
                          :Else ⋄ (⍎nspc).PeerAddr←'Unknown'
                          :EndIf
                          :If Config.Secure
                              rc z←#.DRC.GetProp(2⊃wres)'PeerCert'
                              :If rc=0 ⋄ (⍎nspc).PeerCert←z
                              :Else
                                  1 Log'Unable to obtain peer certificate over secure connection - request ignored'
                                  ⎕EX nspc
                              :EndIf
                          :EndIf
                      :EndTrap
                  :EndIf

                  :If 0≠⎕NC nspc
                      {}(⍎nspc){⎕EX nspc/⍨('BlockLast'≡3⊃⍵)∧⍺ HandleRequest(2↑⍵)}&wres[2 4 3] ⍝ Run page handler in new thread
                  :EndIf
              :Case 'Connect' ⍝ Ignore

              :Else
                  2 Log'Error ',⍕wres
              :EndSelect

          :Case 100 ⍝ Time out - put "housekeeping" code here
              SessionHandler.HouseKeeping ⎕THIS
              :If 0<Config.IdleTimeOut ⍝ if an idle timeout (in seconds) has been specified
              :AndIf Config.IdleTimeOut<86400×-/(ts←#.Dates.DateToIDN ⎕TS)idletime ⍝ has it passed?
                  onIdle
                  idletime←ts
              :EndIf

          :Case 1010 ⍝ Object Not found
              1 Log'Object ''',ServerName,''' has been closed - Web Server shutting down'
              ⎕TKILL CMTid
              →0

          :Else
              1 Log'#.DRC.Wait failed:'
              1 Log wres
          :EndSelect

      :EndWhile

     RESUME: ⍝ Error Trapped and logged
      ⎕TKILL CMTid
      {}#.DRC.Close ServerName
      1 Log r←'Web server ''',ServerName,''' stopped '
      →0

    ∇

    :section Constructor/Destructor

    ∇ Make config;CongaVersion;rc
      :Access Public
      :Implements Constructor

      SessionHandler.GetSession←{}   ⍝ So we can always
      SessionHandler.HouseKeeping←{} ⍝    call these fns
      Authentication.Authenticate←{} ⍝    without worrying
      Logger.Log←{}
      Logger.Stop←{}
      Logger.Start←{}
      :If {0::1 ⋄ 85⌶'0'}'' ⍝ Need a left arg?
          I85←1∘(85⌶)
      :Else
          I85←85⌶
      :EndIf
      Config←config
      :If 0≠⊃rc←¯1 #.DRC.Init''
          ('Unable to initialize Conga. rc=',,⍕rc)⎕SIGNAL 11
      :EndIf

      CongaVersion←{0::0 ⋄ ⊃⊃//⎕VFI ⍵}{(2>+\'.'=⍵)/⍵}⊃{⍵/⍨{∧/⍵∊'0123456789.'}¨⍵}(⊂'')~⍨{1↓¨(⍵=⍬⍴⍵)⊂⍵}' ',2 2⊃#.DRC.Describe'.'
      :If 2.2>CongaVersion
          'MiServer 3.0 requires Conga 2.2 or later'⎕SIGNAL 11
      :EndIf

      'Unable to allocate TCP/IP port'⎕SIGNAL(0≠1⊃,AllocatePort)/11
    ∇

    ∇ UnMake
      :Implements Destructor
      :Trap 0 ⋄ End ⋄ :EndTrap
    ∇

    ∇ r←AllocatePort;ipv;server
    ⍝ Starts Conga, allocates TCP/IP port
      ipv←'Protocol'Config.IPVersion
      :If Config.Secure
          :If ∧/(~0∊⍴)¨Config.(CertFile KeyFile)
          :AndIf ⊃∧/#.Files.Exists¨Config.(CertFile KeyFile)
              {}#.DRC.SetProp'.' 'RootCertDir'Config.RootCertDir
              server←#.DRC.X509Cert.ReadCertFromFile Config.CertFile
              server.KeyOrigin←'DER'Config.KeyFile
              →(0≠1⊃r←#.DRC.Srv'' ''Config.Port'Raw' 10000('X509'server)('SSLValidation'Config.SSLFlags)ipv)⍴0 ⍝ Must have Conga v2.1
              1 Log'Starting secure server using certificate ',Config.CertFile
          :Else
              1 Log'Invalid certificate parameters'
              r←1
              →0
          :EndIf
      :Else
          →(0≠1⊃r←#.DRC.Srv'' ''Config.Port'Raw' 10000 ipv)⍴0
      :EndIf

      ServerName←2⊃r

      ⍝ If running Linux, we may want to switch users (only root can allocate a port less than 1024)

      :If ~isWin
      :AndIf 0<⍴uid←''setting'Config.UserID'
          msg←'4001⌶''',uid,''' '
          :Trap 0
              {}4001⌶uid
          :Else
              1 Log msg,'failed.'
              :Return
          :EndTrap
          4 Log msg,'succeeded.'
      :EndIf

      4 Log'Web server ''',ServerName,''' started on port ',⍕Config.Port
      4 Log'Root folder: ',Config.Root
    ∇

    :endsection

    :section RequestHandling

    ∇ names←pagedata PrepareJSONTargets names;p;m;i;nss;z
     ⍝ See if names contain JSON indexed names like editcell[Name]
     ⍝     and if so convert them to editcell.Name and make sure editcell exists
     ⍝ /// Hack by Morten awaiting cleanup or approval by Brian

      :If 0≠⍴i←(¯1=⎕NC names)/⍳⍴names ⍝ all invalid APL names
          names[i]←('%5B' '%5D'⎕R(,¨'[]'))¨names[i]
      :AndIf 0≠⍴i←i/⍨m←(p←names[i]⍳¨'[')≠∘⊃∘⍴¨names[i] ⍝ ... which contain '['
          nss←∪z←(m/p-1)↑¨names[i] ⍝ all namespaces mentions
          nss pagedata.⎕NS¨⊂⍬      ⍝ prepare empty nss
          names[i]←¯1↓¨names[i]    ⍝ drop trailing ']'
          names[i,¨p]←'.'          ⍝ replace '[' by '.'
      :EndIf
    ∇

    ∇ r←conns HandleRequest arg;buf;m;Answer;obj;CMD;pos;req;Data;z;r;hdr;REQ;status;file;tn;length;done;offset;res;closed;sess;chartype;raw;enc;which;html;encoderc;encodeMe;startsize;cacheMe;root;page;filename;eoh;n;i
      ⍝ Handle a Web Server Request
      r←0
      obj buf←arg
      conns.Buffer,←buf
      conns.Handler←{6::conns.Handler←(⎕UCS'<env>')≡5↑conns.Buffer ⋄ conns.Handler}⍬ ⍝ are we serving as a mapping handler?

      :If 0=conns.Pos ⍝ is we haven't found the end of the header yet...
          eoh←⎕UCS(1+conns.Handler)⊃(NL,NL)('</env>') ⍝ end of header marker
          pos←(¯1+⍴eoh)+eoh FindFirst conns.Buffer
          :If pos≤⍴conns.Buffer ⍝ found the end of header
              conns.Pos←pos
              conns.Req←fromutf8 conns.(Pos↑Buffer)
          :Else
              :Return ⍝ haven't found end of header yet, go back for more
          :EndIf
      :EndIf

      :If 0<⍴conns.Req
          :If conns.Pos>i←(z←LF,'content-length:')FindFirst hdr←#.Strings.lc conns.Req
          :AndIf (⍴conns.Buffer)<conns.Pos+conns.ContentLength←⊃2⊃⎕VFI(¯1+z⍳CR)↑z←(¯1+i+⍴z)↓hdr
              :Return  ⍝ a content-length was specified but we haven't yet gotten what it says to ==> go back for more
          :EndIf
      :EndIf

      conns.Buffer←fromutf8 conns.(Pos↓Buffer)

      :If conns.Handler  ⍝ if we're running as a mapping handler
          conns.(Req Buffer)←MakeHTTPRequest conns.Req ⍝ fake MiServer out by building an HTTP request from what we've got
      :EndIf

⍝      :Hold 'Request'

      REQ←⎕NEW #.HTTPRequest conns.(Req Buffer)
      REQ.Server←⎕THIS ⍝ Request will also contain reference to the Server

      :If 2=conns.⎕NC'PeerAddr' ⋄ REQ.PeerAddr←conns.PeerAddr ⋄ :EndIf       ⍝ Add Client Address Information
      8 Log REQ.(PeerAddr Command Page)

      :If 2=conns.⎕NC'PeerCert' ⋄ REQ.PeerCert←conns.PeerCert ⋄ :EndIf       ⍝ Add Client Cert Information

      :If Config.Rest  ⍝ if running RESTful web service...
          n←+/∧\2>+\REQ.Page∊'/\'
          REQ.RestReq←n↓REQ.Page
          REQ.Page←n↑REQ.Page
      :EndIf

      REQ.Page←Config.DefaultPage{∧/⍵∊'/\':'/',⍺ ⋄ '/\'∊⍨¯1↑⍵:⍵,⍺ ⋄ ⍵}REQ.Page
      REQ.Page,←(~'.'∊{⍵/⍨⌽~∨\'/'=⌽⍵}REQ.Page)/Config.DefaultExtension

      SessionHandler.GetSession REQ
      Authentication.Authenticate REQ

      :If REQ.Response.Status≠401 ⍝ Authentication did not fail

          filename←Config Virtual REQ.Page
          :If Config.AllowedHttpCommands∊⍨⊂REQ.Command

              :If REQ.Page endswith Config.DefaultExtension ⍝ MiPage?
                  filename HandleMSP REQ
              :Else
                  :If REQ.Command≡'get'
                      REQ.ReturnFile filename
                  :Else
                      REQ.Fail 501 ⍝ Service Not Implemented
                  :EndIf
              :EndIf
          :Else
              REQ.Fail 501 ⍝ Service Not Implemented
          :EndIf
      :EndIf

      res←REQ.Response

      encodeMe←conns.Handler<Config.UseContentEncoding ⍝ initialize a flag whether to encode this response, don't compresss if running as a handler
      cacheMe←0

      :If 1=res.File ⍝ See HTTPRequest.ReturnFile
          :If 83=⎕DR file←res.HTML ⍝ if we returned a byte stream, just use it
              length←⍴file ⋄ tn←0
          :Else
              :Trap 22
                  tn←file ⎕NTIE tn←0
                  length←⎕NSIZE tn
                  res.HTML←⎕NREAD tn 83 BlockSize 0
                  cacheMe←0≠Config.HttpCacheTime ⍝ for now, cache all files if set to use caching
                  :If encodeMe
                      encodeMe∧←length<BlockSize ⍝ if we can read entire file and are we encoding...
                      encodeMe>←(⊂¯4↑file)∊'.png' '.gif' '.jpg' ⍝ don't try to compress compressed graphics, should probably add zip files, etc
                  :EndIf
              :Else
                  REQ.Fail 404
                  length←⍴res.HTML
                  res.File←0
              :EndTrap
          :EndIf
          startsize←length
      :EndIf

      :If (200=res.Status)∧encodeMe ⍝ if our HTTP status is 200 (OK) and we're okay to encode
      :AndIf 1024<⍴res.HTML ⍝ BPB used to be 0
      :AndIf 0≠⍴enc←{(⊂'')~⍨1↓¨(⍵=⊃⍵)⊂⍵}' '~⍨',',REQ.GetHeader'accept-encoding' ⍝ check if client supports encoding
      :AndIf 0≠which←⊃Encoders.Encoding{(⍴⍺){(⍺≥⍵)/⍵}⍺⍳⍵}enc ⍝ try to match what encodings they accept to those we provide
          (encoderc html)←Encoders[which].Compress res.HTML
          :If 0=encoderc
              length←startsize←⍴res.HTML
              :If startsize>⍴html ⍝ did we save anything by compressing
                  length←⍴res.HTML←html ⍝ use it
                  res.Headers⍪←'Content-Encoding'(Encoders[which].Encoding)
                  4 Log'Used compression, transmitted% = ',2⍕length{⎕DIV←1 ⋄ ⍺÷⍵}startsize
              :Else
                  4 Log'Compression not used on "',REQ.Page,'", startsize = ',(⍕startsize),', compressed length = ',⍕length
                  :If 83≠⎕DR res.HTML
                      res.HTML←toutf8 res.HTML
                  :EndIf
              :EndIf
          :ElseIf 0=res.File
              2 Log'Compression failed'
              length←⍴res.HTML←toutf8 res.HTML ⍝ otherwise, send uncompressed
          :EndIf
      :ElseIf 0=res.File
          startsize←length←⍴res.HTML←toutf8∊res.HTML
      :EndIf

      :If (200=res.Status)∧cacheMe ⍝ if cacheable, set expires
      :AndIf 0<Config.HttpCacheTime
          res.Headers⍪←'Expires'(Config.HttpCacheTime #.Dates.HttpDate ⎕TS)
      :EndIf
      res.Headers⍪←{0∊⍴⍵:'' '' ⋄ 'Server'⍵}Config.Server
      status←res.((⍕Status),' ',StatusText)
      hdr←enlist{⍺,': ',⍵,NL}/res.Headers
      Answer←(toutf8((1+conns.Handler)⊃'HTTP/1.0 ' 'Status: '),status,NL,'Content-Length: ',(⍕length),NL,hdr,NL),res.HTML
      done←length≤offset←⍴res.HTML
      REQ.MSec-⍨←⎕AI[3]
      res.Bytes←startsize length
      :Repeat
          :If 0≠1⊃z←#.DRC.Send obj Answer done ⍝ Send response and
              (1+(1⊃z)∊1008 1119)Log'"Handlerequest" closed socket ',obj,' due to error: ',(⍕z),' sending response'
              done←1
          :EndIf
          closed←done
          :If ~done
              done←BlockSize>⍴Answer←⎕NREAD tn 83,BlockSize,offset
              offset+←⍴Answer
          :EndIf
      :Until closed ⍝ Everything sent

      :If res.File ⋄ ⎕NUNTIE tn ⋄ :EndIf
      8 Log REQ.PeerAddr status
      Logger.Log REQ
      r←1
 ⍝     :EndHold
    ∇

    ∇ file HandleMSP REQ;⎕TRAP;inst;class;z;props;lcp;args;i;ts;date;n;expired;data;m;oldinst;names;html;sessioned;page;root;fn;MS3;token;cb;mask;resp;t;RESTful;APLJax
    ⍝ Handle a "Mildserver Page" request
     RETRY:
      :If 0≡date←3⊃(,''#.Files.List file),0 0 0
          REQ.Fail 404 ⋄ →0
      :EndIf

      MS3←RESTful←0

      :If sessioned←326=⎕DR REQ.Session ⍝ do we think we have a session handler active?
      :AndIf 0≠⍴REQ.Session.Pages     ⍝ Look for existing Page in Session
      :AndIf (n←⍴REQ.Session.Pages)≥i←REQ.Session.Pages._PageName⍳⊂REQ.Page
          inst←i⊃REQ.Session.Pages ⍝ Get existing instance
          :If expired←inst._PageDate≢date  ⍝ Timestamp unchanged?
          :AndIf expired←(⎕SRC⊃⊃inst)≢#.UnicodeFile.ReadNestedText file
              oldinst←inst
              REQ.Session.Pages~←inst
              4 Log'Page: ',REQ.Page,' ... has been updated ...'
          :EndIf
      :AndIf ~expired
          4 Log'Using existing instance of page: ',REQ.Page
          :If 9=⎕NC'#.HtmlPage'
              :If MS3←∨/(∊⎕CLASS inst)∊#.HtmlPage ⋄ inst._Request←REQ ⋄ :EndIf
          :EndIf
          :If 9=⎕NC'#.RESTful'
              :If RESTful←∨/(∊⎕CLASS inst)∊#.RESTful ⋄ inst._Request←REQ ⋄ :EndIf
          :EndIf
      :Else                        ⍝ First use of Page in this Session, or page expired
          :If 0≠⍴z←#.Files.GetText file
              :Trap 11 22
                  class←⎕SE.SALT.Load file,' -target=#.Pages'
                  inst←⎕NEW class
              :Case 11 ⋄ REQ.Fail 500 ⋄ →0 ⍝ Domain Error: HTTP Internal Error
              :Case 22 ⋄ REQ.Fail 404 ⋄ →0 ⍝ File Name Error: HTTP Page not found
              :EndTrap
              4 Log'Creating new instance of page: ',REQ.Page
              inst._PageName←REQ.Page
              inst._PageDate←date
              MS3←RESTful←0
              :If 9=⎕NC'#.HtmlPage'
                  :If MS3←∨/(∊⎕CLASS inst)∊#.HtmlPage
                  :OrIf RESTful←∨/(∊⎕CLASS inst)∊#.RESTful
                      inst.(_Request _PageRef)←REQ inst
                  :EndIf
              :EndIf
              :If sessioned ⋄ REQ.Session.Pages,←inst ⋄ :EndIf
          :Else
              REQ.Fail 404 ⋄ →0
          :EndIf
      :EndIf

      :If sessioned ⋄ token←REQ.(Page,⍕Session.ID)
      :ElseIf ~0∊⍴REQ.PeerAddr ⋄ token←REQ.(Page,PeerAddr)
      :Else ⋄ token←⍕⎕TID
      :EndIf

      :Hold token

     ⍝ Move arguments / parameters into Public Properties
          inst._PageData←⎕NS''
          :If 0≠1↑⍴data←{⍵[⍋↑⍵[;1];]}REQ.Arguments⍪REQ.Data
              m←1,2≢/data[;1]
              data←(m/data[;1]),[1.5]m⊂data[;2]
              i←{⍵/⍳⍴⍵}1=⊃∘⍴¨data[;2]
              data[i;2]←⊃¨data[i;2]
              :If 0≠⍴lcp←props←('_'≠1⊃¨props)/props←(inst.⎕NL-2) ⍝ Get list of public properties (those beginning with '_' are excluded)
              :AndIf 0≠1↑⍴args←(data[;1]∊lcp)⌿data
                  args←(2⌈⍴args)⍴args
                  i←lcp⍳args[;1]
                  ⍎'inst.(',(⍕props[i]),')←args[;2]'
              :EndIf
              :If ∨/mask←'_'≠1⊃¨data[;1]
                  args←mask⌿data
                  args[;1]←inst._PageData PrepareJSONTargets args[;1]
                  ⍎'inst._PageData.(',(⍕args[;1]),')←args[;2]'
              :EndIf
          :EndIf

          fn←cb←'Render'
          :If APLJax←REQ.isAPLJax>RESTful ⍝ if it's an APLJax (XmlHttpRequest) request (but not web service)
              REQ.Response.NoWrap←1
              fn←cb←'APLJax' ⍝ default callback function name
              :If MS3
                  inst._what←REQ.GetData'_what'
                  inst._event←REQ.GetData'_event'
                  inst._value←REQ.GetData'_value'
                  :If ~0∊⍴t←REQ.GetData'_callback' ⍝ does the request specify a callback function
                      fn←cb←t
                  :EndIf
              :EndIf
          :Else
              :If MS3
                  cb←cb inst.{3=⌊|⎕NC⊂⍵:⍵ ⋄ ⍺}fn←(1+RESTful)⊃'Compose' 'Respond' ⍝ default function to call
              :EndIf
          :EndIf

          :If 3≠⌊|inst.⎕NC⊂cb            ⍝ and is it a public method?
              1 Log'Method "',fn,'" not found (or not public) in page "',REQ.Page,'"'
              REQ.Fail 500
          :EndIf

          :If MS3
              :If (⊂cb)∊'Render' 'Compose'
                  inst._init ⍝ reset instance's content
              :ElseIf APLJax
                  inst._resetAjax
              :EndIf
          :EndIf

          :If (1=Config.TrapErrors)∧9=⎕NC'#.DrA' ⋄ ⎕TRAP←#.DrA.TrapServer
          :ElseIf (0=Config.Production) ⋄ ⎕TRAP←(800 'C' '→FAIL')(811 'E' '⎕SIGNAL 801')(813 'E' '⎕SIGNAL 803')(812 'S')(0 'E' '⍎#.Boot.Oops') ⍝ enable development debug framework
          :EndIf

          :Trap 85   ⍝ we use 85⌶ because "old" MiPages use REQ.Return internally (and don't return a result)...
              resp←I85'inst.',cb,(MS3⍱RESTful)/' REQ'  ⍝ ... whereas "new" MiPages return the HTML they generate
              resp←(#.JSON.toAPLJAX⍣APLJax)resp
              REQ.Return resp
          :Else
              :If APLJax
                  1 Log'No result returned by callback method "',cb,'" in page "',REQ.Page,'"'
                  REQ.Return''
              :EndIf
          :EndTrap

          :If ~REQ.Response.NoWrap
              :If MS3∨RESTful
                  inst.Wrap
              :Else
                  inst.Wrap REQ
              :EndIf
              :If Config.FormatHtml
                  :Trap 0
                      REQ.Response.HTML←('.'⎕R'&'⍠'NEOL' 1⍠'EOL' 'LF')((⎕XML⍠'Whitespace' 'Preserve')⍣2)REQ.Response.HTML
                  :Else
                      ⎕←'*** ⎕XML failed'
                  :EndTrap
              :EndIf
          :EndIf
      :EndHold
      →0

     FAIL:
      ⎕TRAP←0⍴⎕TRAP
      REQ.Fail 500 ⋄ →0

     RESUME: ⍝ RESUME is used by DrA
      ⎕TRAP←0/⎕TRAP ⍝ Let framework trapping take over

      :If #.DrA.UseHTTP ⋄ REQ.Fail 500 ⋄ →0 ⋄ :EndIf

      REQ.Title'Unhandled Execution Error'
      REQ.Style'Styles/error.css'
      html←'h1'#.HTMLInput.Enclose'Server Error in ''',REQ.Page,'''.
'
      html,←'
Unhandled Exception Error
'
      html,←'Description: An unhandled exception occurred during the execution of the current web request.'
      :If #.DrA.Mode=2 ⍝ Allows editing
          html,←'

Edit page: ',REQ.Page,'
'
      :EndIf
      html,←'

Exception Details:

'
      :If (#.DrA.Mode>0)∧0≠⍴#.DrA.LastFile ⋄ html,←#.DrA.(GenHTML LastFile)
      :Else ⋄ html,←'code'#.HTMLInput.Enclose'',(⊃,/#.DrA.LastError,¨⊂'
'),''
      :EndIf
      REQ.Return html
    ∇

    ∇ (req buffer)←MakeHTTPRequest req;x;v;s;p;l;m;n;i;c;h
⍝ kludge to get by ampersands in a POST - will be fixed when we build proper requests from MiServerCGI
      buffer←c←''
      :If (⍴req)≥i←1⍳⍨'>tsop<'⍷⌽req
          i←(⍴req)-i+5
          c←¯13↓(i+6)↓req
          req←(i↑req),'</env>'
      :EndIf

      :Trap 11
          x←⎕XML req
      :Else
          ∘∘∘
      :EndTrap
      v←'var'∘≡¨x[;2]
      v←{⎕ML←3 ⋄ (~<\'='=⍵)⊂⍵}¨v/x[;3]
      v←(∊2=⍴¨v)/v
      h←∊{0∊⍴⍵:'' ⋄ (#.Strings.lc{'HTTP_'≡5↑⍵:5↓⍵ ⋄ 'HTTPS_'≡6↑⍵:6↓⍵ ⋄ ⍵}⍺){('-',⍺)[('_',⍺)⍳⍺],': ',⍵,NL}⍵}/¨v
      ⍝ pull out special fields
      m p s n←(↑v)∘{3::2⊃⍵ ⋄ ⍺[;2]⊃⍨⍺[;1]⍳⊂1⊃⍵}¨↓'REQUEST_METHOD' 'PATH_INFO' 'SERVER_PROTOCOL' 'SERVER_NAME',[1.1]'GET' '' 'HTTP/1.0' 'localhost'
      (req buffer)←(m,' ',p,' ',s,NL,'Host: ',n,h,NL)c
    ∇

    :endsection

    :section Misc

    ∇ ConnectionMonitor server
    ⍝ Because AJAX calls don't send a "BlockLast" packet, we need to clean up connection namespaces that didn't get erased
      :While 1
          ⎕DL 5
          {}Common.{⎕EX(⎕NL ¯9)~'C',¨#.DRC.Names ⍵}server
      :EndWhile
    ∇

    ∇ r←SpaceName cmd
     ⍝ Generate namespace name from command name
      r←'Common.C',Subst(2⊃{1↓¨('.'=⍵)⊂⍵}'.',cmd)'-=' '_∆'
    ∇

    ∇ r←Subst arg;i;m;str;c;rep
      ⍝ Substitute character c in str with rep
      str c rep←arg
      i←c⍳str
      m←i≤⍴c
      (m/str)←rep[m/i]
      r←str
    ∇

    ∇ file←Config Virtual page;mask;f;ind;t;path;root
      :Access public shared
    ⍝ checks for virtual directory
      root←(-'/\'∊⍨¯1↑root)↓root←Config.Root
      page←('/\'∊⍨1↑page)↓page
      file←root,'/',page
      :If 0<⍴Config.Virtual
          ind←Config.Virtual.alias⍳⊂t←{(¯1+⍵⍳'/')⍴⍵}page
          :If ind≤⍴Config.Virtual.alias
              path←ind⊃Config.Virtual.path
              file←#.Files.unixfix path,('/\'∊⍨¯1↑path)↓(⍴t)↓page
          :EndIf
      :EndIf
    ∇

    ∇ r←isWin
      r←'Win'≡3↑1⊃'.'⎕WG'APLVersion'
    ∇

    :endsection

:EndClass

:Class MiServer

    :Field Public Config

    :Field Public TID←¯1                ⍝ Thread ID Server is running under
    :Field Public BlockSize←640000      ⍝ Blocksize when returning files

    :Field Public SessionHandler←⎕NS ''
    :Field Public Authentication←⎕NS ''
    :Field Public Logger←⎕NS ''
    :Field Public Application←⎕NS ''

    :field Public Encoders←⍬  ⍝ pointers to instances of content encoders
    :field Public Datasources←⍬

    ⎕TRAP←0/⎕TRAP ⋄ (⎕ML ⎕IO)←1 1

    unicode←80=⎕DR 'A'
    NL←(CR LF)←⎕UCS 13 10
    FindFirst←{(⍺⍷⍵)⍳1}
    enlist←{⎕ML←1 ⋄ ∊⍵}
    fromutf8←{0::(⎕AV,'?')[⎕AVU⍳⍵] ⋄ 'UTF-8' ⎕UCS ⍵} ⍝ Turn raw UTF-8 input into text
    toutf8←{'UTF-8' ⎕UCS ⍵}                          ⍝ Turn text into UTF-8 byte stream
    setting←{0=⎕NC ⍵:⍺ ⋄ ⍎⍵}
    endswith←{(,⍺){⍵≡⍺↑⍨-⍴⍵},⍵}
    I85←{}

      bit←{⎕IO←0  ⍝ used by Log
          0=⍺:0 ⍝ all bits turned off
          ¯1=⍺:1 ⍝ all bits turned on
          (⌈2⍟⍵)⊃⌽((1+⍵)⍴2)⊤⍺}

    :section Override
⍝ ↓↓↓--- Methods which are usually overridden ---

    ∇ onServerStart
      :Access Public Overridable
    ⍝ Handle any server startup processing
    ∇

    ∇ onSessionStart req
      :Access Public Overridable
    ⍝ Process a new session
    ∇

    ∇ onSessionEnd session
      :Access Public Overridable
    ⍝ Handle the end of a session
    ∇

    ∇ onIdle
      :Access Public Overridable
    ⍝ Idle time handler - called when the server has gone idle for a period of time
    ∇

    ∇ Error req
      :Access Public Overridable
    ⍝ Handle trapped errors
      req.Response.HTML←'',(⊃,/⎕DM,¨⊂'
'),''
      req.Fail 500 ⍝ Internal Server Error
      1 Log ⎕DM
    ∇

    ∇ level Log msg
      :Access Public overridable
    ⍝ Logs server messages
    ⍝ levels implemented in MildServer are:
    ⍝ 1-error/important, 2-warning, 4-informational, 8-transaction (GET/POST)
      :If Config.LogMessageLevel bit level ⍝ if set to display this level of message
          ⎕←msg ⍝ display it
      :EndIf
    ∇

    ∇ Cleanup
      :Access Public overridable
    ⍝ Perform any site specific cleanup
    ∇

⍝ ↑↑↑--- End of Overridable methods ---
    :endsection

⍝ ↓↓↓--- Begin MildServer Core Code
    :section Start/Stop
    ∇ Run
      :Access Public
      ('Already Running on Thread',⍕TID)⎕SIGNAL(TID∊⎕TNUMS)/11
      TID←RunServer&⍬
    ∇

    ∇ End;tempfiles
    ⍝ Called by destructor
      :Access Public
      Logger.Stop ⍬
      :If 0=⎕NC'ServerName'
         ⍝!!! {}1 #.DRC.Init''
      :Else
          'Not running'⎕SIGNAL(#.DRC.Exists ServerName)↓11
          {}#.DRC.Close ServerName
          :Trap 6 ⍝ ⎕TSYNC may not return a value if the thread doesn't, so handle the possible VALUE ERROR
              {}⎕TSYNC ⎕TID~⍨TID∩⎕TNUMS
          :EndTrap
      :EndIf
      :If ~0∊⍴Config.TempFolder
          tempfiles←{(~⍵[;4])/⍵[;1]}#.Files.List Config.TempFolder
          :If 0≠⍴tempfiles←(tempfiles∨.≠¨'.')/tempfiles
              {}{0::'' ⋄ #.Files.Delete Config.TempFolder,⍵}¨tempfiles
          :EndIf
      :EndIf
      Cleanup ⍝ overridable
      TID←¯1
    ∇
    :endsection

    ∇ r←RunServer arg;RESUME;⎕TRAP;Common;cmd;name;port;wres;ref;nspc;sink;Stop;secure;certpath;flags;z;rc;rate;ErrorCount;idletime;msg;uid;ts;CMTid
      ⍝ Simple HTTP (Web) Server framework
      ⍝ Assumes Conga available in #.DRC and uses #.HTTPRequest
      ⍝ arg: dummy
      ⍝ certs: RootCertDir, ServerCert, ServerKey (optional: runs Secure server)

      Common←⎕NS'' ⋄ Stop←0 ⋄ WAITING←0
      CMTid←ConnectionMonitor&ServerName

      :If Config.TrapErrors>0
          ⎕TRAP←#.DrA.TrapServer
          #.DrA.NoUser←1+#.DrA.MailRecipient∨.≠' '
      :EndIf ⍝ Trap MiServer Errors. See HandleMSP for Page Errors.

      onServerStart ⍝ meant to be overridden

      idletime←#.Dates.DateToIDN ⎕TS
      :While ~Stop
          wres←#.DRC.Wait ServerName 20000 ⍝ Tick every 20 secs
          ⍝ wres: (return code) (object name) (command) (data)

          :Select 1⊃wres
          :Case 0 ⍝ Good data from RPC.Wait
              :Select 3⊃wres

              :Case 'Error'
                  :If ServerName≡2⊃wres
                      Stop←1
                  :EndIf
                  (1+(4⊃wres)∊1008 1105 1119)Log'RunServer: DRC.Wait reported error ',(⍕#.DRC.Error 4⊃wres),' on ',2⊃wres

                  ⎕EX SpaceName 2⊃wres

              :CaseList 'Block' 'BlockLast'
                  nspc←SpaceName 2⊃wres
                  :If 0=⎕NC nspc
                      :Trap 6 ⍝ VALUE ERROR - nspc could be erased if page goes away
                          nspc ⎕NS''
                          (⍎nspc).(Pos Buffer ContentLength Req)←0 '' 0 ''
                          :If 0=1⊃z←#.DRC.GetProp(2⊃wres)'PeerAddr' ⋄ (⍎nspc).PeerAddr←2⊃z
                          :Else ⋄ (⍎nspc).PeerAddr←'Unknown'
                          :EndIf
                          :If Config.Secure
                              rc z←#.DRC.GetProp(2⊃wres)'PeerCert'
                              :If rc=0 ⋄ (⍎nspc).PeerCert←z
                              :Else
                                  1 Log'Unable to obtain peer certificate over secure connection - request ignored'
                                  ⎕EX nspc
                              :EndIf
                          :EndIf
                      :EndTrap
                  :EndIf

                  :If 0≠⎕NC nspc
                      {}(⍎nspc){⎕EX nspc/⍨('BlockLast'≡3⊃⍵)∧⍺ HandleRequest(2↑⍵)}&wres[2 4 3] ⍝ Run page handler in new thread
                  :EndIf
              :Case 'Connect' ⍝ Ignore

              :Else
                  2 Log'Error ',⍕wres
              :EndSelect

          :Case 100 ⍝ Time out - put "housekeeping" code here
              SessionHandler.HouseKeeping ⎕THIS
              :If 0<Config.IdleTimeOut ⍝ if an idle timeout (in seconds) has been specified
              :AndIf Config.IdleTimeOut<86400×-/(ts←#.Dates.DateToIDN ⎕TS)idletime ⍝ has it passed?
                  onIdle
                  idletime←ts
              :EndIf

          :Case 1010 ⍝ Object Not found
              1 Log'Object ''',ServerName,''' has been closed - Web Server shutting down'
              ⎕TKILL CMTid
              →0

          :Else
              1 Log'#.DRC.Wait failed:'
              1 Log wres
          :EndSelect

      :EndWhile

     RESUME: ⍝ Error Trapped and logged
      ⎕TKILL CMTid
      {}#.DRC.Close ServerName
      1 Log r←'Web server ''',ServerName,''' stopped '
      →0

    ∇

    :section Constructor/Destructor

    ∇ Make config;CongaVersion;rc
      :Access Public
      :Implements Constructor

      SessionHandler.GetSession←{}   ⍝ So we can always
      SessionHandler.HouseKeeping←{} ⍝    call these fns
      Authentication.Authenticate←{} ⍝    without worrying
      Logger.Log←{}
      Logger.Stop←{}
      Logger.Start←{}
      :If {0::1 ⋄ 85⌶'0'}'' ⍝ Need a left arg?
          I85←1∘(85⌶)
      :Else
          I85←85⌶
      :EndIf
      Config←config
      :If 0≠⊃rc←¯1 #.DRC.Init''
          ('Unable to initialize Conga. rc=',,⍕rc)⎕SIGNAL 11
      :EndIf

      CongaVersion←{0::0 ⋄ ⊃⊃//⎕VFI ⍵}{(2>+\'.'=⍵)/⍵}⊃{⍵/⍨{∧/⍵∊'0123456789.'}¨⍵}(⊂'')~⍨{1↓¨(⍵=⍬⍴⍵)⊂⍵}' ',2 2⊃#.DRC.Describe'.'
      :If 2.2>CongaVersion
          'MiServer 3.0 requires Conga 2.2 or later'⎕SIGNAL 11
      :EndIf

      'Unable to allocate TCP/IP port'⎕SIGNAL(0≠1⊃,AllocatePort)/11
    ∇

    ∇ UnMake
      :Implements Destructor
      :Trap 0 ⋄ End ⋄ :EndTrap
    ∇

    ∇ r←AllocatePort;ipv;server
    ⍝ Starts Conga, allocates TCP/IP port
      ipv←'Protocol'Config.IPVersion
      :If Config.Secure
          :If ∧/(~0∊⍴)¨Config.(CertFile KeyFile)
          :AndIf ⊃∧/#.Files.Exists¨Config.(CertFile KeyFile)
              {}#.DRC.SetProp'.' 'RootCertDir'Config.RootCertDir
              server←#.DRC.X509Cert.ReadCertFromFile Config.CertFile
              server.KeyOrigin←'DER'Config.KeyFile
              →(0≠1⊃r←#.DRC.Srv'' ''Config.Port'Raw' 10000('X509'server)('SSLValidation'Config.SSLFlags)ipv)⍴0 ⍝ Must have Conga v2.1
              1 Log'Starting secure server using certificate ',Config.CertFile
          :Else
              1 Log'Invalid certificate parameters'
              r←1
              →0
          :EndIf
      :Else
          →(0≠1⊃r←#.DRC.Srv'' ''Config.Port'Raw' 10000 ipv)⍴0
      :EndIf

      ServerName←2⊃r

      ⍝ If running Linux, we may want to switch users (only root can allocate a port less than 1024)

      :If ~isWin
      :AndIf 0<⍴uid←''setting'Config.UserID'
          msg←'4001⌶''',uid,''' '
          :Trap 0
              {}4001⌶uid
          :Else
              1 Log msg,'failed.'
              :Return
          :EndTrap
          4 Log msg,'succeeded.'
      :EndIf

      4 Log'Web server ''',ServerName,''' started on port ',⍕Config.Port
      4 Log'Root folder: ',Config.Root
    ∇

    :endsection

    :section RequestHandling

    ∇ names←pagedata PrepareJSONTargets names;p;m;i;nss;z
     ⍝ See if names contain JSON indexed names like editcell[Name]
     ⍝     and if so convert them to editcell.Name and make sure editcell exists
     ⍝ /// Hack by Morten awaiting cleanup or approval by Brian

      :If 0≠⍴i←(¯1=⎕NC names)/⍳⍴names ⍝ all invalid APL names
          names[i]←('%5B' '%5D'⎕R(,¨'[]'))¨names[i]
      :AndIf 0≠⍴i←i/⍨m←(p←names[i]⍳¨'[')≠∘⊃∘⍴¨names[i] ⍝ ... which contain '['
          nss←∪z←(m/p-1)↑¨names[i] ⍝ all namespaces mentions
          nss pagedata.⎕NS¨⊂⍬      ⍝ prepare empty nss
          names[i]←¯1↓¨names[i]    ⍝ drop trailing ']'
          names[i,¨p]←'.'          ⍝ replace '[' by '.'
      :EndIf
    ∇

    ∇ r←conns HandleRequest arg;buf;m;Answer;obj;CMD;pos;req;Data;z;r;hdr;REQ;status;file;tn;length;done;offset;res;closed;sess;chartype;raw;enc;which;html;encoderc;encodeMe;startsize;cacheMe;root;page;filename;eoh;n;i
      ⍝ Handle a Web Server Request
      r←0
      obj buf←arg
      conns.Buffer,←buf
      conns.Handler←{6::conns.Handler←(⎕UCS'<env>')≡5↑conns.Buffer ⋄ conns.Handler}⍬ ⍝ are we serving as a mapping handler?

      :If 0=conns.Pos ⍝ is we haven't found the end of the header yet...
          eoh←⎕UCS(1+conns.Handler)⊃(NL,NL)('</env>') ⍝ end of header marker
          pos←(¯1+⍴eoh)+eoh FindFirst conns.Buffer
          :If pos≤⍴conns.Buffer ⍝ found the end of header
              conns.Pos←pos
              conns.Req←fromutf8 conns.(Pos↑Buffer)
          :Else
              :Return ⍝ haven't found end of header yet, go back for more
          :EndIf
      :EndIf

      :If 0<⍴conns.Req
          :If conns.Pos>i←(z←LF,'content-length:')FindFirst hdr←#.Strings.lc conns.Req
          :AndIf (⍴conns.Buffer)<conns.Pos+conns.ContentLength←⊃2⊃⎕VFI(¯1+z⍳CR)↑z←(¯1+i+⍴z)↓hdr
              :Return  ⍝ a content-length was specified but we haven't yet gotten what it says to ==> go back for more
          :EndIf
      :EndIf

      conns.Buffer←fromutf8 conns.(Pos↓Buffer)

      :If conns.Handler  ⍝ if we're running as a mapping handler
          conns.(Req Buffer)←MakeHTTPRequest conns.Req ⍝ fake MiServer out by building an HTTP request from what we've got
      :EndIf

⍝      :Hold 'Request'

      REQ←⎕NEW #.HTTPRequest conns.(Req Buffer)
      REQ.Server←⎕THIS ⍝ Request will also contain reference to the Server

      :If 2=conns.⎕NC'PeerAddr' ⋄ REQ.PeerAddr←conns.PeerAddr ⋄ :EndIf       ⍝ Add Client Address Information
      8 Log REQ.(PeerAddr Command Page)

      :If 2=conns.⎕NC'PeerCert' ⋄ REQ.PeerCert←conns.PeerCert ⋄ :EndIf       ⍝ Add Client Cert Information

      :If Config.Rest  ⍝ if running RESTful web service...
          n←+/∧\2>+\REQ.Page∊'/\'
          REQ.RestReq←n↓REQ.Page
          REQ.Page←n↑REQ.Page
      :EndIf

      REQ.Page←Config.DefaultPage{∧/⍵∊'/\':'/',⍺ ⋄ '/\'∊⍨¯1↑⍵:⍵,⍺ ⋄ ⍵}REQ.Page
      REQ.Page,←(~'.'∊{⍵/⍨⌽~∨\'/'=⌽⍵}REQ.Page)/Config.DefaultExtension

      SessionHandler.GetSession REQ
      Authentication.Authenticate REQ

      :If REQ.Response.Status≠401 ⍝ Authentication did not fail

          filename←Config Virtual REQ.Page
          :If Config.AllowedHttpCommands∊⍨⊂REQ.Command

              :If REQ.Page endswith Config.DefaultExtension ⍝ MiPage?
                  filename HandleMSP REQ
              :Else
                  :If REQ.Command≡'get'
                      REQ.ReturnFile filename
                  :Else
                      REQ.Fail 501 ⍝ Service Not Implemented
                  :EndIf
              :EndIf
          :Else
              REQ.Fail 501 ⍝ Service Not Implemented
          :EndIf
      :EndIf

      res←REQ.Response

      encodeMe←conns.Handler<Config.UseContentEncoding ⍝ initialize a flag whether to encode this response, don't compresss if running as a handler
      cacheMe←0

      :If 1=res.File ⍝ See HTTPRequest.ReturnFile
          :If 83=⎕DR file←res.HTML ⍝ if we returned a byte stream, just use it
              length←⍴file ⋄ tn←0
          :Else
              :Trap 22
                  tn←file ⎕NTIE tn←0
                  length←⎕NSIZE tn
                  res.HTML←⎕NREAD tn 83 BlockSize 0
                  cacheMe←0≠Config.HttpCacheTime ⍝ for now, cache all files if set to use caching
                  :If encodeMe
                      encodeMe∧←length<BlockSize ⍝ if we can read entire file and are we encoding...
                      encodeMe>←(⊂¯4↑file)∊'.png' '.gif' '.jpg' ⍝ don't try to compress compressed graphics, should probably add zip files, etc
                  :EndIf
              :Else
                  REQ.Fail 404
                  length←⍴res.HTML
                  res.File←0
              :EndTrap
          :EndIf
          startsize←length
      :EndIf

      :If (200=res.Status)∧encodeMe ⍝ if our HTTP status is 200 (OK) and we're okay to encode
      :AndIf 1024<⍴res.HTML ⍝ BPB used to be 0
      :AndIf 0≠⍴enc←{(⊂'')~⍨1↓¨(⍵=⊃⍵)⊂⍵}' '~⍨',',REQ.GetHeader'accept-encoding' ⍝ check if client supports encoding
      :AndIf 0≠which←⊃Encoders.Encoding{(⍴⍺){(⍺≥⍵)/⍵}⍺⍳⍵}enc ⍝ try to match what encodings they accept to those we provide
          (encoderc html)←Encoders[which].Compress res.HTML
          :If 0=encoderc
              length←startsize←⍴res.HTML
              :If startsize>⍴html ⍝ did we save anything by compressing
                  length←⍴res.HTML←html ⍝ use it
                  res.Headers⍪←'Content-Encoding'(Encoders[which].Encoding)
                  4 Log'Used compression, transmitted% = ',2⍕length{⎕DIV←1 ⋄ ⍺÷⍵}startsize
              :Else
                  4 Log'Compression not used on "',REQ.Page,'", startsize = ',(⍕startsize),', compressed length = ',⍕length
                  :If 83≠⎕DR res.HTML
                      res.HTML←toutf8 res.HTML
                  :EndIf
              :EndIf
          :ElseIf 0=res.File
              2 Log'Compression failed'
              length←⍴res.HTML←toutf8 res.HTML ⍝ otherwise, send uncompressed
          :EndIf
      :ElseIf 0=res.File
          startsize←length←⍴res.HTML←toutf8∊res.HTML
      :EndIf

      :If (200=res.Status)∧cacheMe ⍝ if cacheable, set expires
      :AndIf 0<Config.HttpCacheTime
          res.Headers⍪←'Expires'(Config.HttpCacheTime #.Dates.HttpDate ⎕TS)
      :EndIf
      res.Headers⍪←{0∊⍴⍵:'' '' ⋄ 'Server'⍵}Config.Server
      status←res.((⍕Status),' ',StatusText)
      hdr←enlist{⍺,': ',⍵,NL}/res.Headers
      Answer←(toutf8((1+conns.Handler)⊃'HTTP/1.0 ' 'Status: '),status,NL,'Content-Length: ',(⍕length),NL,hdr,NL),res.HTML
      done←length≤offset←⍴res.HTML
      REQ.MSec-⍨←⎕AI[3]
      res.Bytes←startsize length
      :Repeat
          :If 0≠1⊃z←#.DRC.Send obj Answer done ⍝ Send response and
              (1+(1⊃z)∊1008 1119)Log'"Handlerequest" closed socket ',obj,' due to error: ',(⍕z),' sending response'
              done←1
          :EndIf
          closed←done
          :If ~done
              done←BlockSize>⍴Answer←⎕NREAD tn 83,BlockSize,offset
              offset+←⍴Answer
          :EndIf
      :Until closed ⍝ Everything sent

      :If res.File ⋄ ⎕NUNTIE tn ⋄ :EndIf
      8 Log REQ.PeerAddr status
      Logger.Log REQ
      r←1
 ⍝     :EndHold
    ∇

    ∇ file HandleMSP REQ;⎕TRAP;inst;class;z;props;lcp;args;i;ts;date;n;expired;data;m;oldinst;names;html;sessioned;page;root;fn;MS3;token;cb;mask;resp;t;RESTful;APLJax
    ⍝ Handle a "Mildserver Page" request
     RETRY:
      :If 0≡date←3⊃(,''#.Files.List file),0 0 0
          REQ.Fail 404 ⋄ →0
      :EndIf

      MS3←RESTful←0

      :If sessioned←326=⎕DR REQ.Session ⍝ do we think we have a session handler active?
      :AndIf 0≠⍴REQ.Session.Pages     ⍝ Look for existing Page in Session
      :AndIf (n←⍴REQ.Session.Pages)≥i←REQ.Session.Pages._PageName⍳⊂REQ.Page
          inst←i⊃REQ.Session.Pages ⍝ Get existing instance
          :If expired←inst._PageDate≢date  ⍝ Timestamp unchanged?
              oldinst←inst
              REQ.Session.Pages~←inst
              4 Log'Page: ',REQ.Page,' ... has been updated ...'
          :EndIf
      :AndIf ~expired
          4 Log'Using existing instance of page: ',REQ.Page
          :If 9=⎕NC'#.HtmlPage'
              :If MS3←∨/(∊⎕CLASS inst)∊#.HtmlPage ⋄ inst._Request←REQ ⋄ :EndIf
          :EndIf
          :If 9=⎕NC'#.RESTful'
              :If RESTful←∨/(∊⎕CLASS inst)∊#.RESTful ⋄ inst._Request←REQ ⋄ :EndIf
          :EndIf
      :Else                        ⍝ First use of Page in this Session, or page expired
          :If 0≠⍴z←#.Files.GetText file
              :Trap 11 22
                  class←⎕SE.SALT.Load file,' -target=#.Pages'
                  inst←⎕NEW class
              :Case 11 ⋄ REQ.Fail 500 ⋄ →0 ⍝ Domain Error: HTTP Internal Error
              :Case 22 ⋄ REQ.Fail 404 ⋄ →0 ⍝ File Name Error: HTTP Page not found
              :EndTrap
              4 Log'Creating new instance of page: ',REQ.Page
              inst._PageName←REQ.Page
              inst._PageDate←date
              MS3←RESTful←0
              :If 9=⎕NC'#.HtmlPage'
                  :If MS3←∨/(∊⎕CLASS inst)∊#.HtmlPage
                  :OrIf RESTful←∨/(∊⎕CLASS inst)∊#.RESTful
                      inst.(_Request _PageRef)←REQ inst
                  :EndIf
              :EndIf
              :If sessioned ⋄ REQ.Session.Pages,←inst ⋄ :EndIf
          :Else
              REQ.Fail 404 ⋄ →0
          :EndIf
      :EndIf

      :If sessioned ⋄ token←REQ.(Page,⍕Session.ID)
      :ElseIf ~0∊⍴REQ.PeerAddr ⋄ token←REQ.(Page,PeerAddr)
      :Else ⋄ token←⍕⎕TID
      :EndIf

      :Hold token

     ⍝ Move arguments / parameters into Public Properties
          inst._PageData←⎕NS''
          :If 0≠1↑⍴data←{⍵[⍋↑⍵[;1];]}REQ.Arguments⍪REQ.Data
              m←1,2≢/data[;1]
              data←(m/data[;1]),[1.5]m⊂data[;2]
              i←{⍵/⍳⍴⍵}1=⊃∘⍴¨data[;2]
              data[i;2]←⊃¨data[i;2]
              :If 0≠⍴lcp←props←('_'≠1⊃¨props)/props←(inst.⎕NL-2) ⍝ Get list of public properties (those beginning with '_' are excluded)
              :AndIf 0≠1↑⍴args←(data[;1]∊lcp)⌿data
                  args←(2⌈⍴args)⍴args
                  i←lcp⍳args[;1]
                  ⍎'inst.(',(⍕props[i]),')←args[;2]'
              :EndIf
              :If ∨/mask←'_'≠1⊃¨data[;1]
                  args←mask⌿data
                  args[;1]←inst._PageData PrepareJSONTargets args[;1]
                  ⍎'inst._PageData.(',(⍕args[;1]),')←args[;2]'
              :EndIf
          :EndIf

          fn←cb←'Render'
          :If APLJax←REQ.isAPLJax>RESTful ⍝ if it's an APLJax (XmlHttpRequest) request (but not web service)
              REQ.Response.NoWrap←1
              fn←cb←'APLJax' ⍝ default callback function name
              :If MS3
                  inst._what←REQ.GetData'_what'
                  inst._event←REQ.GetData'_event'
                  inst._value←REQ.GetData'_value'
                  :If ~0∊⍴t←REQ.GetData'_callback' ⍝ does the request specify a callback function
                      fn←cb←t
                  :EndIf
              :EndIf
          :Else
              :If MS3
                  cb←cb inst.{3=⌊|⎕NC⊂⍵:⍵ ⋄ ⍺}fn←(1+RESTful)⊃'Compose' 'Respond' ⍝ default function to call
              :EndIf
          :EndIf

          :If 3≠⌊|inst.⎕NC⊂cb            ⍝ and is it a public method?
              1 Log'Method "',fn,'" not found (or not public) in page "',REQ.Page,'"'
              REQ.Fail 500
          :EndIf

          :If MS3
              :If (⊂cb)∊'Render' 'Compose'
                  inst._init ⍝ reset instance's content
              :ElseIf APLJax
                  inst._resetAjax
              :EndIf
          :EndIf

          :If (1=Config.TrapErrors)∧9=⎕NC'#.DrA' ⋄ ⎕TRAP←#.DrA.TrapServer
          :ElseIf (0=Config.Production) ⋄ ⎕TRAP←(800 'C' '→FAIL')(811 'E' '⎕SIGNAL 801')(813 'E' '⎕SIGNAL 803')(812 'S')(0 'E' '⍎#.Boot.Oops') ⍝ enable development debug framework
          :EndIf

          :Trap 85   ⍝ we use 85⌶ because "old" MiPages use REQ.Return internally (and don't return a result)...
              resp←I85'inst.',cb,(MS3⍱RESTful)/' REQ'  ⍝ ... whereas "new" MiPages return the HTML they generate
              resp←(#.JSON.toAPLJAX⍣APLJax)resp
              REQ.Return resp
          :Else
              :If APLJax
                  1 Log'No result returned by callback method "',cb,'" in page "',REQ.Page,'"'
                  REQ.Return''
              :EndIf
          :EndTrap

          :If ~REQ.Response.NoWrap
              :If MS3∨RESTful
                  inst.Wrap
              :Else
                  inst.Wrap REQ
              :EndIf
              :If Config.FormatHtml
                  :Trap 0
                      REQ.Response.HTML←('.'⎕R'&'⍠'NEOL' 1⍠'EOL' 'LF')((⎕XML⍠'Whitespace' 'Preserve')⍣2)REQ.Response.HTML
                  :Else
                      ⎕←'*** ⎕XML failed'
                  :EndTrap
              :EndIf
          :EndIf
      :EndHold
      →0

     FAIL:
      ⎕TRAP←0⍴⎕TRAP
      REQ.Fail 500 ⋄ →0

     RESUME: ⍝ RESUME is used by DrA
      ⎕TRAP←0/⎕TRAP ⍝ Let framework trapping take over

      :If #.DrA.UseHTTP ⋄ REQ.Fail 500 ⋄ →0 ⋄ :EndIf

      REQ.Title'Unhandled Execution Error'
      REQ.Style'Styles/error.css'
      html←'h1'#.HTMLInput.Enclose'Server Error in ''',REQ.Page,'''.
'
      html,←'
Unhandled Exception Error
'
      html,←'Description: An unhandled exception occurred during the execution of the current web request.'
      :If #.DrA.Mode=2 ⍝ Allows editing
          html,←'

Edit page: ',REQ.Page,'
'
      :EndIf
      html,←'

Exception Details:

'
      :If (#.DrA.Mode>0)∧0≠⍴#.DrA.LastFile ⋄ html,←#.DrA.(GenHTML LastFile)
      :Else ⋄ html,←'code'#.HTMLInput.Enclose'',(⊃,/#.DrA.LastError,¨⊂'
'),''
      :EndIf
      REQ.Return html
    ∇

    ∇ (req buffer)←MakeHTTPRequest req;x;v;s;p;l;m;n;i;c;h
⍝ kludge to get by ampersands in a POST - will be fixed when we build proper requests from MiServerCGI
      buffer←c←''
      :If (⍴req)≥i←1⍳⍨'>tsop<'⍷⌽req
          i←(⍴req)-i+5
          c←¯13↓(i+6)↓req
          req←(i↑req),'</env>'
      :EndIf

      :Trap 11
          x←⎕XML req
      :Else
          ∘∘∘
      :EndTrap
      v←'var'∘≡¨x[;2]
      v←{⎕ML←3 ⋄ (~<\'='=⍵)⊂⍵}¨v/x[;3]
      v←(∊2=⍴¨v)/v
      h←∊{0∊⍴⍵:'' ⋄ (#.Strings.lc{'HTTP_'≡5↑⍵:5↓⍵ ⋄ 'HTTPS_'≡6↑⍵:6↓⍵ ⋄ ⍵}⍺){('-',⍺)[('_',⍺)⍳⍺],': ',⍵,NL}⍵}/¨v
      ⍝ pull out special fields
      m p s n←(↑v)∘{3::2⊃⍵ ⋄ ⍺[;2]⊃⍨⍺[;1]⍳⊂1⊃⍵}¨↓'REQUEST_METHOD' 'PATH_INFO' 'SERVER_PROTOCOL' 'SERVER_NAME',[1.1]'GET' '' 'HTTP/1.0' 'localhost'
      (req buffer)←(m,' ',p,' ',s,NL,'Host: ',n,h,NL)c
    ∇

    :endsection

    :section Misc

    ∇ ConnectionMonitor server
    ⍝ Because AJAX calls don't send a "BlockLast" packet, we need to clean up connection namespaces that didn't get erased
      :While 1
          ⎕DL 5
          {}Common.{⎕EX(⎕NL ¯9)~'C',¨#.DRC.Names ⍵}server
      :EndWhile
    ∇

    ∇ r←SpaceName cmd
     ⍝ Generate namespace name from command name
      r←'Common.C',Subst(2⊃{1↓¨('.'=⍵)⊂⍵}'.',cmd)'-=' '_∆'
    ∇

    ∇ r←Subst arg;i;m;str;c;rep
      ⍝ Substitute character c in str with rep
      str c rep←arg
      i←c⍳str
      m←i≤⍴c
      (m/str)←rep[m/i]
      r←str
    ∇

    ∇ file←Config Virtual page;mask;f;ind;t;path;root
      :Access public shared
    ⍝ checks for virtual directory
      root←(-'/\'∊⍨¯1↑root)↓root←Config.Root
      page←('/\'∊⍨1↑page)↓page
      file←root,'/',page
      :If 0<⍴Config.Virtual
          ind←Config.Virtual.alias⍳⊂t←{(¯1+⍵⍳'/')⍴⍵}page
          :If ind≤⍴Config.Virtual.alias
              path←ind⊃Config.Virtual.path
              file←#.Files.unixfix path,('/\'∊⍨¯1↑path)↓(⍴t)↓page
          :EndIf
      :EndIf
    ∇

    ∇ r←isWin
      r←'Win'≡3↑1⊃'.'⎕WG'APLVersion'
    ∇

    :endsection

:EndClass


MarkAPL classic fix

MarkAPL classic fix

:Class MarkAPL
⍝⍝⍝!:require APLTreeUtils
⍝ MarkAPL is a parser that converts Markdown to valid HTML5.
⍝
⍝ Most importantly: get help with
⍝
⍝ ~~~
⍝ #.MarkAPL.Help 0
⍝ ~~~
⍝
⍝ This will work if the `Files\` folder lives in the current directory.
⍝ If it doesn't then either make sure that it does or create a parameter
⍝ space by calling `CreateHelpParms` and then set `homeFolder` to the path
⍝ that points to the `Files\` folder.
⍝
⍝ This document contains detailed information.
⍝
⍝ Examples:
⍝
⍝ ~~~
⍝ MyMarkdown←'# MarkAPL' 'All about **_MarkAPL_**'
⍝ ~~~
⍝
⍝ ~~~
⍝ (html ns)←#.MarkAPL.Markdown2HTML MyMarkdown
⍝ ~~~
⍝
⍝ This converts `MyMarkdown` into HTML but it does not create a fully-fledged
⍝ HTML page. You can achive that by:
⍝
⍝ ~~~
⍝ parms←#.MarkAPL.CreateParms
⍝ parms.inputFilename←'in.md'
⍝ parms.outputFilename←'out.md'
⍝ (html ns)←parms #.MarkAPL.Markdown2HTML ''
⍝ ~~~
⍝
⍝ Homepage: <http://aplwiki.com/MarkAPL>
⍝
⍝ Kai Jaeger ⋄ APL Team Ltd
    ⎕IO←1 ⋄ ⎕ML←1

    :Include APLTreeUtils

    ∇ r←Version
      :Access Public Shared
      ⍝ * 1.8.4
      ⍝   * "_italic_." switched  on but no off.
      ⍝ * 1.8.3
      ⍝   * Functions are not compiled for the time being; see bug <01164>.
      ⍝   * Documentation improved.
      ⍝   * Bug fix: a table with just a header made MarkAPL crash.
      ⍝ * 1.8.2
      ⍝   * Something like #._fnsName is now recognized as a compound name and NOT marked up with .
      ⍝   * Both `**` and `*` are now allowed within names.
      ⍝ * 1.8.1
      ⍝   * Bug fix:
      ⍝     * Recognizing embedded functions did not always work properly.
      ⍝ * 1.8.0
      ⍝   * Definition lists improved.
      r←(Last⍕⎕THIS)'1.8.4' '2016-07-23'
    ∇

    :Field Public Shared ReadOnly PartOfNames←⎕A,⎕D,'_∆⍙','qwertyuiopasdfghjklzxcvbnm'
    :Field Public Shared ReadOnly AllWhiteSpaceChars←⎕ucs 32 9 10 11 13

    ⎕IO←1 ⋄ ⎕ML←1

    ∇ {(html ns)}←{parms}Markdown2HTML markdown
    ⍝ ⍵ is one of:
    ⍝   * Vector of character vectors representing a Markdown document.
    ⍝   * Empty vector. In this case the parameter "inputFilename" must be specified.
    ⍝ ⍺ Is a namespace with parameters, typically create by calling `CreateParms`.
    ⍝ ← Is a two-element vector starting from version 1.7.0:
    ⍝   [1] Is always the HTML created. As a side effect this HTML will also be written
    ⍝       to the file specified by `outputFilename` - if that is not empty that is.
    ⍝   [2] The `ns` namespace created by `Init` and needed/processed by `Process. This contains `ns.report`,
    ⍝        something you might want to check.
      :Access Public Shared
      parms←{0<⎕NC ⍵:⍎⍵ ⋄ CreateParms}'parms'
      :If 0∊⍴markdown
          :If 0∊⍴parms.inputFilename
              'Neither "markdown" nor "inputFilename" defined'⎕SIGNAL 6
          :Else
              markdown←ReadUtf8File parms.inputFilename
          :EndIf
      :EndIf
      ns←Init parms markdown
      ns←Process ns
      html←ns.html
      :If (~0∊⍴ns.parms.outputFilename)∧0≠ns.parms.createFullHtmlPage
      :OrIf 1=ns.parms.createFullHtmlPage
          html←ns.parms MakeHTML_Doc html
      :EndIf
      {}WriteUtf8File⍣(~0∊⍴ns.parms.outputFilename)⊣ns.parms.outputFilename html
    ∇

    ∇ r←parms MakeHTML_Doc html
    ⍝ Takes HTML, typically created by calling `Process`, and makes it a fully fledged document by adding
    ⍝ ,  -- with <title> -- and  including the DocType. By default CSS is injected as well.
      :Access Public Shared
      r←'<!DOCTYPE html>' '' ''
      r,←⊂'<meta charset="',parms.charset,'">'
      :If ~0∊⍴parms.head
          r,←Nest parms.head
      :EndIf
      r,←⊂'<title>',parms.title,'</title>'
      :If parms.linkToCSS
          r,←⊂'<link rel="stylesheet" media="screen" href="',parms.(cssURL,screenCSS),'">'
          :If ~0∊⍴parms.printCSS
              r,←⊂'<link rel="stylesheet" media="print" href="',parms.(cssURL,printCSS),'">'
          :EndIf
      :Else
          :If ~0∊⍴parms.screenCSS
              r,←(⊂'')
          :EndIf
          :If ~0∊⍴parms.printCSS
              r,←(⊂'')
          :EndIf
      :EndIf
      r,←'' '',html,'' ''
    ∇

    ∇ r←CreateParms;clp;SetTo
    ⍝ Returns a parameter namespace with default values.
    ⍝ Use method `∆List` to list all names and their values.
      :Access Public Shared
      r←⎕NS''
      clp←GetCommandLineParms''
      SetTo←{0=clp.⎕NC ⍺:⍎'r.',⍺,'←⍵' ⋄ ⍎'r.',⍺,'←clp.⍎⍺'}  ⍝ Take commannd line parms or default
      'body'SetTo''
      'bookmarkLink'SetTo 6
      r.debug←IsDevelopment
      'charset'SetTo'utf-8'
      'checkFootnotes'SetTo r.debug
      'checkLinks'SetTo r.debug
      'compileFunctions'SetTo 1
      'createFullHtmlPage'SetTo ¯1
      'cssURL'SetTo ¯1
      'head'SetTo''
      'homeFolder'SetTo ¯1
      'inputFilename'SetTo''
      'linkToCSS'SetTo 0
      'markdownStrict'SetTo 0
      'numberHeaders'SetTo 0
      'outputFilename'SetTo''
      'printCSS'SetTo'MarkAPL_print.css'
      'screenCSS'SetTo'MarkAPL_screen.css'
      'subTocs'SetTo 1
      'title'SetTo'MarkAPL'
      'toc'SetTo 0
      'tocCaption'SetTo'Table of contents'
      'verbose'SetTo r.debug
      'bookmarkMayStartWithDigit'SetTo 1
      r.⎕FX'r←∆List;⎕IO' '⍝ List all variables and possible references in this namespace' '⎕IO←1' 'r←{⍵,[1.5]⍎¨⍵}⎕NL-2 9'
    ∇

    ∇ parms←CreateHelpParms
      :Access Public Shared
      parms←CreateParms
      parms.linkToCSS←0
      parms.toc←2 3 4
      parms.numberHeaders←2 3 4 5
      parms.bookmarkLink←6
      parms.verbose←0
      parms.compileFunctions←0
      parms.viewInBrowser←1
    ∇

    ∇ ns←Process ns
    ⍝ Takes a namespace, typically created by calling `Init`, and then processes `ns.markdown`,
    ⍝ creating ns.html along the way.
    ⍝
    ⍝ The result is all MarkDown converted to HTML but not a fully-fledged HTML document. In order to
    ⍝ achieve that call `MakeHTML_Doc` in the next step.
      :Access Public Shared
      ns.noOf←1               ⍝ Minimum number of lines to be removed from ns.(markdown leadingChars emptyLines) per cycle.
      ns←ScanMarkdown ns
      ns←NumberHeaders ns
      ns←InjectTOC ns
      ns←InjectSubTOCs ns
      ns←InjectFootNotes ns
      ns←ReplaceLinkIDs ns
      ns←HandleAbbreviations ns
      ns←CheckInternalLinks ns
      ns←CheckForInvalidFootnotes ns
      :If ns.parms.verbose
          ⎕←⍪ns.report
      :EndIf
      ns.html←{1=≡⍵:⍵ ⋄ ⊃,/⊃⍵}¨ns.html
    ∇

    ∇ ns←Init(parms markdown);buffer
    ⍝ Creates a namespace "ns" that contains important stuff needed to process `markdown`.
    ⍝ See "MarkAPL.html" for details.
      :Access Public Shared
      :If 0∊⍴parms
          parms←CreateParms
      :EndIf
      parms←EstablishDefaultHomeFolder parms
      :If ¯1≡parms.cssURL
          parms.cssURL←parms.homeFolder
      :EndIf
      {}CompileMarkAPL parms
      :If 0∊⍴markdown
          'Neither "markdown" nor "inputFilename" are set?!'⎕SIGNAL 6/⍨0∊⍴parms.inputFilename
          markdown←ReadUtf8File parms.inputFilename
      :EndIf
      'Invalid Markdown (depth)'⎕SIGNAL 11/⍨2≠|≡markdown
      markdown←,¨markdown
      'Invalid Markdown (depth)'⎕SIGNAL 11/⍨(,1)≢∪≡¨markdown
      markdown←'\t'⎕R(4⍴' ')⍠('Mode' 'M')⊣markdown  ⍝ Replace all <TAB> chars by 4 spaces
      parms.(inputFilename outputFilename cssURL screenCSS printCSS)←CorrectSlash¨parms.(inputFilename outputFilename cssURL screenCSS printCSS)
      ns←Create_NS ⍬
      ns.parms←parms
      markdown←RemoveAllComments markdown
      ns.markdown←(Nest markdown),⊂''
      ns.markdown←{0=+/b←(⎕UCS 0)=w←⍵:w ⋄ (b/w)←⎕UCS 65533}¨ns.markdown ⍝ Replace U+0000 by U+FFFD for secutity reasons
      ns.markdownLC←Lowercase ns.markdown           ⍝ We need this often, so we do this ONCE
      buffer←dlb ns.markdown
      ns.emptyLines←0=⊃∘⍴∘,¨buffer
      ns.leadingChars←(16⌊⍴∘,¨ns.markdown)↑¨buffer
      ns.lineNumbers←⍳⍴ns.leadingChars              ⍝ Useful for reporting problems
      ns.withoutBlanks←ns.markdown~¨' '
      ns.abbreviations←0 2⍴''
      ns.linkRefs←⍬
      ns.data←⍬
      ns.(subToc toc)←⊂''
      :If (,0)≢,parms.toc
          parms.bookmarkLink⌈←⌈/parms.toc
      :EndIf
      parms.head←Nest parms.head
    ∇

    ∇ r←Execute y
    ⍝ This is used for test purposes only.
      :Access Public Shared
      'Invalid call'⎕SIGNAL 11/⍨~IsDevelopment
      :If 0=⎕NC'ns'
          ⎕SHADOW'ns'
          ns←Create_NS ⍬
      :EndIf
      r←⍎y
    ∇

    ∇ {ns}←{parms}Help recompileFlag;ps;parms;fn
    ⍝ Displays the file MarkAPL.html with your default browser
    ⍝
    ⍝ `recompileFlag`:
    ⍝ - A zero shows the file as it stands.
    ⍝ - A 1 lets MarkAPL recompile it from MarkAPL.md first.
    ⍝ Returns either `⍬` or the `ns` namepsace created by `Init` and modified by `Process`.
      :Access Public Shared
      ns←⍬
      parms←{0<⎕NC ⍵:⍎⍵ ⋄ CreateHelpParms}'parms'
      :If ⍬≡parms
          fn←GetCurrentDir,'\Files\MarkAPL.html'
      :Else
          parms←EstablishDefaultHomeFolder parms
          :If ¯1≡parms.cssURL
              parms.cssURL←parms.homeFolder
          :EndIf
          fn←CorrectSlash parms.homeFolder,'\MarkAPL.html'
      :EndIf
      :If 0∊⍴parms.inputFilename
          parms.inputFilename←(¯4↓fn),'md'
      :EndIf
      :If 0∊⍴parms.outputFilename
          parms.outputFilename←fn
      :EndIf
      :If 0=DoesFileExist parms.inputFilename
          6 ⎕SIGNAL⍨'File "',parms.inputFilename,'" not found; set "homeFolder"'
      :EndIf
      :If |recompileFlag
          :If ⍬≡parms
              parms←CreateHelpParms
              parms.inputFilename←(¯4↓fn),'md'
              parms.outputFilename←fn
          :Else
              :If 0∊⍴parms.outputFilename
                  parms.outputFilename←fn
              :Else
                  fn←parms.outputFilename
              :EndIf
          :EndIf
          ns←2⊃parms #.MarkAPL.Markdown2HTML''
      :EndIf
      :If ¯1≠×recompileFlag  ⍝ This syntax is used only by the `Make` workspace and test cases, therefore it is not documented.
      :AndIf parms.viewInBrowser
          GoToWebPage'file://',fn
      :EndIf
    ∇

⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
⍝                                                                                ⍝
⍝                                                   Private stuff                                           ⍝
⍝                                                                                ⍝
⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    ∇ r←ProcessLists ns;noOf
      r←0
      :If 3 IsHtmlList⊃ns.markdown
          r←ProcessList ns
      :EndIf
    ∇

    ∇ r←ProcessDefinitionLists ns;noOf;bool;bl;html;nop;not2;buff;bl2;total;el;not;sa;colons
      r←0
      :If 1<⍴ns.markdown                                ⍝ A definitions has at least two lines
      :AndIf 1≤ScanForPara ns                           ⍝ Exactly one line for a term but there might be no empty line after it.
      :AndIf ~0∊⍴'^\s{0,3}:\s'⎕S 0⊣⊃(+/∧\1↓ns.emptyLines)↓1↓ns.markdown  ⍝ Is there ": " in the first non-empty line?
          noOf←(1 1⍷ns.emptyLines)⍳1                    ⍝ Maximum number of lines until the last `:` of this definition list
          colons←': '∘≡¨2↑¨noOf↑ns.leadingChars         ⍝ Where are any leading colons followed by a space?
          total←0
      :AndIf 0<+/colons                                 ⍝ Any at all? If not it's not a definition list!
          bl←noOf↑ns.markdown                           ⍝ The whole lot
          sa←GetSpecialAttributes⊃bl
          (⊃bl)←sa DropSpecialAttributes⊃bl
          html←⊂'
' ⋄ sa,'>'}sa)
          el←{0=⊃¨⍴¨⍵}bl~¨' '                           ⍝ Empty lines
          :Repeat
              :If 1<⍴bl
              :AndIf ~0∊⍴'^\s{0,3}:\s'⎕S 0⊣{⍵⊃⍨1⍳⍨0<⊃¨⍴¨⍵}1↓bl
                  buff←'dt'∘Tag¨ns ProcessInlineMarkUp¨1↑bl
                  not←1++/∧\1↓el
                  (bl colons el)←not↓¨bl colons el
              :AndIf 0∊el
              ⍝ Now we pick up everything that fulfills one of the following conditions:
              ⍝ * Starts with a ": "
              ⍝ * Starts with two spaces (plus any leading spaces before the ":")
              ⍝ * Is an empty line
                  nop←+/∧\el∨colons∨{⊃(' '=¯1↓⍵)∧' '≠¯1↑⍵}¨(3++/∧\' '=⊃bl)↑¨bl
              :AndIf 0<⍴bl2←nop↑bl
                  (bl colons el)←(⍴bl2)↓¨bl colons el
              :AndIf ~0∊⍴bl2←(0<⊃¨⍴¨bl2~¨' ')/bl2
                  bl2{v←⍺ ⋄ 0∊⍴⍵:v ⋄ ((¯1+⊃⍵)⊃v)←dmb((¯1+⊃⍵)⊃v),' ',(⊃⍵)⊃v ⋄ ((⊃⍵)⊃v)←'' ⋄ v ∇ 1↓⍵}←⌽Where':'≠⊃¨dlb¨bl2
                  bl2←(0<⊃∘⍴¨bl2)/bl2
                  bl2←{⍵↓⍨1+⍵⍳':'}¨bl2
                  bl2←ns ProcessInlineMarkUp¨bl2
                  :If 1=not
                  :AndIf 1=⍴bl2
                      bl2←⊂'dd'Tag⊃bl2
                  :Else
                      bl2←'dd'∘Tag¨Tag¨bl2
                  :EndIf
                  html,←buff,bl2
                  total+←not+nop
              :Else
                  bl←''
              :EndIf
          :Until 0∊⍴bl
          ns.noOf←total
          html,←⊂'
'
          r←1
          ns.html,←html
          ns←Drop ns
      :EndIf
    ∇

    ∇ r←ProcessBlockQuotes ns;parms;md;ns2
    ⍝ Processes any blockquotes recursively. That way they dan contain **everything**, including blockquotes.
      r←0
      :If '> '≡2↑⊃ns.leadingChars
          ns.noOf←ScanForBlockQuotes ns
          parms←CreateParms
          parms.bookmarkLink←0
          parms.markdownStrict←ns.parms.markdownStrict
          parms.verbose←0
          parms.checkLinks←0
          parms.checkFootnotes←0
          parms.subTocs←0
          md←ns.noOf↑ns.markdown
          (1⊃md)←2↓1⊃md
          (1↓md)←(2×'> '∘≡¨2↑¨1↓md)↓¨1↓md
          ns2←Init parms md
          ns2←Process ns2
          ns.html,←(⊂'
'),ns2.html,⊂'
'
          ns.report,←(⊃ns.lineNumbers)PolishLineNumbersInReport ns2.report
          ns←Drop ns
          r←1
      :EndIf
    ∇

    ∇ r←ProcessList ns;bl;type;i;startAt;item;pFlag;noOfBlanks;levels;levelChange;buff;sa;lastType;lastItem;toBeAdded;indentations;html;lastWasEmpty;indendations;cb;drop;para;Max
    ⍝ Processing lists is more complex than one would think at first glance for several reasons:
    ⍝ * Lists can be nested at any level.
    ⍝ * A nested list can have a different type.
    ⍝ * Changing the bullet char for bulleted lists (+-*) starts a new list!
    ⍝ * Lists may contain independent paragrahs; their level is defined by indenting.
    ⍝ * MarkAPL allows lazy indenting: if a list items spans over several lines only the first
    ⍝   line must be indented properly. All other lines may or may not be indented.
    ⍝ * A single backslash at the end of an item are interpreted as "inject
 here". So is
    ⍝   the insertion of `<<br>>` anywhere in the code but `<<br>>` is actually handled at a later stage.
    ⍝      {0=#.⎕NC ⍵: ⋄ #.STOP:.}'STOP'
      :If 0=ns.noOf←IdentifyListItems ns
          r←0
      :Else
          type←,(1+(⊃⊃ns.leadingChars)∊'*+-')⌷'ou'
          bl←GetListBlock ns.noOf↑ns.markdown
          :If type='u'
              html←,⊂'
'
          :Else
              startAt←{⍵↑⍨¯1+⌊/⍵⍳'.)'}dlb⊃bl
              html←,⊂'
'
          :EndIf
          i←lastWasEmpty←levelChange←0
    ⍝ `levels` and `indentation` record almost the same thing:
    ⍝ * `level` counts white-space until the first non-white-space character.
    ⍝ * `indentation` does the same but then adds the length of the list-marker.
    ⍝ The latter is needed in order to identify the level of paragraphs and code blocks within a list.
          levels←+/∧\' '=⊃bl
          indendations←GetLengthOfLeadingWhitespacePlusListMarker⊃bl
          lastType←⊃dlb⊃bl
          :Repeat
              drop←1
              i←i+1
              :If IsHtmlList dlb⊃bl
                  noOfBlanks←+/∧\' '=⊃bl
                  :If noOfBlanks≠¯1↑levels
                      :If noOfBlanks>¯1↑levels
                          type,←(1+(⊃i⊃ns.leadingChars)∊'*+-')⌷'ou'
                          :If 'u'=¯1↑type
                              html,←⊂'
'
                          :Else
                              startAt←{⍵↑⍨¯1+⌊/⍵⍳'.)'}dlb⊃bl
                              html,←⊂'
'
                          :EndIf
                          levels,←noOfBlanks
                          indendations,←GetLengthOfLeadingWhitespacePlusListMarker⊃bl
                          lastType,←⊃dlb⊃bl
                      :Else
                          levelChange←+/∧\(⌽levels)>noOfBlanks
                          html,←{'</',(¯1↑⍵),'l>'}¨⌽(-levelChange)↑type
                          (levels indendations lastType type)←(-levelChange)↓¨levels indendations lastType type
                      :EndIf
                  :ElseIf ~0∊⍴⊃bl
                  :AndIf (¯1↑lastType){~⍺∊'+-*':~⍵∊⎕D ⋄ ⍺≠⍵}⊃dlb⊃bl
                      html,←⊂'</',(¯1↑type),'l>'
                      ((⍴type)⊃type)←(1+(⊃i⊃ns.leadingChars)∊'*+-')⌷'ou'
                      lastType,←⊃dlb⊃bl
                      :If 'u'=¯1↑type
                          html,←⊂'
'
                      :Else
                          startAt←{⍵↑⍨¯1+⍵⍳'.'}dlb⊃bl
                          html,←⊂'
'
                      :EndIf
                  :EndIf
              :EndIf
              :If 0∊⍴' '~⍨⊃bl
                  lastWasEmpty←1
              :Else
                  :If IsHtmlList dlb⊃bl
                      (drop item)←CollectItem bl
                      :If ~0∊⍴sa←GetSpecialAttributes⊃bl
                          item←dlb dtb sa DropSpecialAttributes item
                          :If (⊂3↑(⍴html)⊃html)∊'<ul' '
'
                          :EndIf
                      :Else
                          item←dlb dtb item
                      :EndIf
                      i←i+drop-1
                      item←ns CheckOddNumberOfDoubleQuotes item'list item'
                      :If '\'=¯1↑item
                          item,←'
'
                      :EndIf
                      html,←⊂'
',ns ProcessInlineMarkUp{dlb ⍵↓⍨⍵⍳' '},item
                      lastWasEmpty←0
                  :Else
                      noOfBlanks←+/∧\' '=⊃bl
                      :If (¯1↑indendations){0=⍵:0 ⋄ ⍵≠⍺}noOfBlanks          ⍝ The level has changed
                          :If noOfBlanks∊indendations                       ⍝ Did the user get the indentation right?
                              levelChange←¯1+(⌽indendations)⍳noOfBlanks
                          :Else                                             ⍝ No he did not.
                              levelChange←0⌈¯1++/indendations<noOfBlanks    ⍝ Let's get as close as possible
                          :EndIf
                          :If 0≠levelChange
                              html,←{'</',⍵,'l>'}¨(-levelChange)↑type
                              (type levels indendations lastType)←(-levelChange)↓¨type levels indendations lastType
                          :EndIf
                      :EndIf
                      :If lastWasEmpty
                          buff←⊃bl
                          :If {((⊂3↑⍵~' ')∊'~~~' '```')∧∨/(⊂∪⍵)≡¨,¨'~`'}{⍵↑⍨¯1+⍵⍳'{'}' '~⍨⊃bl ⍝ Is it a code block?!
                              cb←(2⍳⍨+\({⍵↑⍨3⌊⍴⍵}¨bl~¨' ')∊'~~~' '```')↑bl  ⍝ Seems to be a code block
                          :AndIf (+/∧\' '=⊃cb)=⊃noOfBlanks                  ⍝ Max number of spaces is the indentation
                              sa←GetSpecialAttributes⊃cb
                              cb←¯1↓1↓cb
                              :If 0<+/⊃¨⍴¨cb~¨' '
                                  html,←MarkUpAsCode(EscapeSpecialChars¨(⊃noOfBlanks)↓¨cb)sa
                              :EndIf
                              drop←2+⍴cb
                              i+←1+⍴cb
                          :Else
                              (drop para)←CollectItemPara bl
                              i←i+drop-1
                              sa←GetSpecialAttributes para
                              para←dtb sa DropSpecialAttributes para
                              html,←⊂'
',(ns ProcessInlineMarkUp para),'
'
                          :EndIf
                      :EndIf
                      lastWasEmpty←0
                  :EndIf
              :EndIf
              bl←drop↓bl
          :Until 0∊⍴bl
          html,←{'</',(¯1↑⍵),'l>'}¨⌽type
          html←InjectBR¨html
          ns.html,←html
          ns←Drop ns
          r←1
      :EndIf
    ∇

      GetLengthOfLeadingWhitespacePlusListMarker←{
        ⍝ 9 ← ∇ '  123)   List item'
        ⍝ 0 ← ∇ 'List item'
          ⊃'^\s*?\b\d{1,9}\b[.)]\s+' '^\s*?[-*+]{1}\s+'⎕S 1⊢⍵
      }

      ProcessHorizontalRulers←{
          ns←⍵
          3<+/∧\' '=⊃ns.markdown:0              ⍝ Zero to a maximum of three leading white space characters are allowed.
          sa←GetSpecialAttributes⊃ns.markdown
          0=+/∧/¨(⊂sa DropSpecialAttributes' '~⍨⊃ns.leadingChars)='*-_':0
          line←dlb⊃ns.markdown
          line←sa DropSpecialAttributes line
          3>line+.=⊃line:0
          ~0∊⍴line~' ',⊃line:0
          ns.html,←⊂'
'
          ns←Drop ns
          1
      }

    ∇ r←ProcessTable ns;b;max
      r←0
      :If IsTableRow⊃ns.markdown
          max←+/∧\∨/'|'=↑ns.withoutBlanks
          ns.noOf←+/∧\IsTableRow¨max↑ns.markdown
          :If 1<ns.noOf
          :AndIf ∨/{∧/'-'='|: '~⍨⍵}¨2↑ns.markdown
              r←ProcessTable_ ns
          :Else
              r←ProcessTableWithoutColTitles ns
          :EndIf
      :EndIf
    ∇

    ∇ r←ProcessTable_ ns;specialAttrs;ind;align;drop;cells;rows
      :Trap (~ns.parms.debug)/0
          specialAttrs←GetSpecialAttributes⊃ns.markdown
          ns.html,←⊂{0∊⍴⍵:'
' ⋄ '
'}specialAttrs
          :If {∧/'-'='|: '~⍨⍵}2⊃ns.markdown
              ns.html,←⊂'
'
              ns.html,←⊂'
'
              ind←{~':'∊⍵:1 ⋄ '::'≡2⍴¯1⌽⍵:3 ⋄ ':'=1⍴⍵:2 ⋄ 4}¨' '~¨⍨SplitTableRowButMaskCode 2⊃ns.markdown
              align←{' style="text-align: ',⍵,';"'}∘↑¨ind⌷¨⊂'left' 'left' 'center' 'right'
              cells←SplitTableRowButMaskCode specialAttrs DropSpecialAttributes 1⊃ns.markdown
              cells←ns{⍺ CheckOddNumberOfDoubleQuotes ⍵'header'}¨cells
              cells←ns ProcessInlineMarkUp¨cells
              :If ~ns.parms.markdownStrict
                  cells←SmartQuotes¨cells
              :EndIf
              ns.html,←('th'∘{⍺,⍵}¨align)Tag¨(⍴align)↑cells,(⍴align)⍴⊂''
              ns.html,←⊂'
'
              ns.html,←⊂'
'
              drop←2
          :Else
              ind←{~':'∊⍵:1 ⋄ '::'≡2⍴¯1⌽⍵:3 ⋄ ':'=1⍴⍵:2 ⋄ 4}¨' '~¨⍨SplitTableRowButMaskCode 1⊃ns.markdown
              align←{' style="text-align: ',⍵,';"'}∘↑¨ind⌷¨⊂'left' 'left' 'center' 'right'
              drop←1
          :EndIf
          ns.html,←⊂'
'
          :If ~0∊⍴cells←drop↓ns.noOf↑ns.markdown
              cells←SplitTableRowButMaskCode¨drop↓ns.noOf↑ns.markdown
              cells←{dlb∘dtb ⍵}¨¨cells
              cells←ns{⍺ CheckOddNumberOfDoubleQuotes ⍵'header'}¨¨cells
              cells←ns ProcessInlineMarkUp¨¨cells
              rows←{('td'∘{⍺,⍵}¨align)Tag¨⍵}¨(⍴align)↑¨cells
              ns.html,←⊃,/{(⊂'
'),⍵,⊂'
'}¨rows
          :EndIf
          ns.html,←⊂'
'
          ns.html,←⊂'
'
          ns←Drop ns
          r←1
      :Else
          r←1
          ns←Drop ns
      :EndTrap
    ∇

      ProcessTableWithoutColTitles←{
          ns←⍵
          specialAttrs←GetSpecialAttributes⊃ns.markdown
          ns.html,←⊂{0∊⍴⍵:'
' ⋄ '
'}specialAttrs
          ns.html,←⊂'
'
          cells←SplitTableRowButMaskCode¨{(⊂specialAttrs DropSpecialAttributes⊃⍵),1↓⍵}ns.noOf↑ns.markdown
          cells←{dlb∘dtb ⍵}¨¨cells
          cells←ns{⍺ CheckOddNumberOfDoubleQuotes ⍵'header'}¨¨cells
          cells←ns ProcessInlineMarkUp¨¨cells
          rows←{'td'∘Tag¨⍵}¨(⊃⌈/⍴¨cells)↑¨cells
          ns.html,←⊃,/{(⊂'
'),⍵,⊂'
'}¨rows
          ns.html,←⊂'
'
          ns.html,←⊂'
'
          ns←Drop ns
          1
      }

      ProcessHeaders←{
          ns←⍵
          '#'=1⍴⊃ns.markdown:ProcessATX_Header ns
          ProcessSetextHeader ns
      }

      ProcessATX_Header←{
          ns←⍵
          l←+/∧\'#'=1⊃ns.leadingChars                 ⍝ Level
          ~l∊⍳6:'Invalid header level'⎕SIGNAL 11
          txt←1⊃ns.markdown
          ~4>+/∧\' '=txt:0                            ⍝ A maximum of three spaces is allowed
          ' '≠⊃txt~'#':0                              ⍝ The first char after the # must be a blank
          sa←GetSpecialAttributes⊃ns.markdown
          txt←sa DropSpecialAttributes txt
          c←{⍵↓⍨-+/∧\(⌽⍵)∊'# '}{⍵↓⍨+/∧\⍵∊'# '}txt     ⍝ Caption. Delete leading and trailing blanks in the process
          c←ns CheckOddNumberOfDoubleQuotes c'header'
          c2←ns ProcessInlineMarkUp c
          bookmarkName←ns GetBookMarkNameFromCaption txt((l≤ns.parms.bookmarkLink)/sa)
          anchor←{0∊⍴⍵:⍵ ⋄ ⊂''}AddBookmarkLink c2 l ns bookmarkName
          ns.html,←anchor
          ns.html,←⊂'<h',(⍕l),(RemoveIdFromSpecialAttributes⍣(~0∊⍴anchor)⊣sa),'>',c2,'</h',(⍕l),'>'
          ns.html,←((,0)≢,ns.parms.bookmarkLink)/⊂''
          ns.headers⍪←l bookmarkName c2
          ns.noOf←1
          ns.toc,←⊂l c bookmarkName 1
          ns←Drop ns
          1
      }

      ProcessSetextHeader←{
          ns←⍵
          noOf←ScanForPara ns                          ⍝ Because only what qualifies as paragraph can be an ATX header
          noOf=0:0
          ind←noOf+Where∊'-='IotaSetextHeader¨⊂(1+noOf)⌷¨ns.(withoutBlanks markdown emptyLines)
          0∊⍴ind:0                                      ⍝ It's not a Setext header
          sa←GetSpecialAttributes⊃ns.markdown
          l←1+'-'=⊃ind⊃ns.leadingChars
          c←CompilePara noOf↑ns.markdown
          c←sa DropSpecialAttributes c
          ns.noOf←1+noOf
          c2←ns ProcessInlineMarkUp c
          c←ns CheckOddNumberOfDoubleQuotes c'header'
          bookmarkName←ns GetBookMarkNameFromCaption c2((l≤ns.parms.bookmarkLink)/sa)
          anchor←{0∊⍴⍵:⍵ ⋄ ⊂''}AddBookmarkLink c2 l ns bookmarkName
          ns.html,←anchor
          ns.html,←⊂'<h',(⍕l),(RemoveIdFromSpecialAttributes⍣(~0∊⍴anchor)⊣sa),'>',c2,'</h',(⍕l),'>'
          ns.html,←((,0)≢,ns.parms.bookmarkLink)/⊂''
          ns.headers⍪←l bookmarkName c2
          ns.toc,←⊂l c bookmarkName 2
          ns←Drop ns
          1
      }

    ∇ r←RemoveLampLines ns
      r←0
      :If '⍝'=⊃⊃ns.markdown
          ns←Drop ns
          r←1
      :EndIf
    ∇

    ∇ r←ProcessCodeBlock ns;bl;buff;sa;pattern
    ⍝ Handles code block, either "~~~" (Markdown2 Extra) or "```" (Git).
      r←0
      :If 1=⍴'^\s{0,3}[~|`]{3,}\s{0,}({.*?})?\s{0,}$'⎕S 0⊣⊃ns.markdown
          pattern←('`~'[1+'~'∊⊃ns.markdown]),'{3,}'
      :AndIf ¯1≢ns.noOf←pattern FindFenceEnd 1↓ns.markdown
          :If 2<ns.noOf
              bl←1↓¯1↓ns.noOf↑ns.markdown
              sa←GetSpecialAttributes⊃ns.markdown
              :If 0<+/⊃¨⍴¨bl~¨' '
                  bl←((+/∧\' '=⊃ns.markdown)⌊+/∧\' '=↑bl)↓¨bl   ⍝ Drop as many blanks as there are indendet blanks
                  ns.html,←MarkUpAsCode(EscapeSpecialChars¨bl)sa
              :EndIf
              ns.noOf←2+⍴bl
          :ElseIf 2≠ns.noOf
          :OrIf ~0∊⍴(⊃ns.markdown)~'~` '
              ns.noOf←0
              :Return
          :EndIf
          r←1
          ns←Drop ns
      :EndIf
    ∇

      Drop←{
          ns←⍵
          ns.(markdown markdownLC leadingChars emptyLines withoutBlanks lineNumbers)←ns.noOf↓¨ns.(markdown markdownLC leadingChars emptyLines withoutBlanks lineNumbers)
          ns.noOf←1
          ns
      }

    ∇ html←ns ProcessFunctionCalls html;mask;ind;noOf;call;result;flag1;flag2
      mask←~GetMaskForCodeTags html
      :If ~0∊⍴ind←'[^⍎]⍎⍎[^⍎]'⎕S 0⊣' ',(mask/html),' '  ⍝ Two blanks for the ≠ to fit start and end. The first one also fixes ⎕io
      :AndIf ~0∊⍴ind←(2×⌊0.5×⍴ind)↑ind
          :Repeat
              html←(⊃ind)⌽html
              noOf←-/ind[2 1]
              call←dlb dtb 1↓(noOf-1)↑1↓html
              html←(2+noOf)↓html
              :If 0∊⍴result←ns ExecExternalFns call
                  :Return
              :EndIf
              flag1←⊃~ns.parms.markdownStrict
              flag2←FunctionCallResultIsHtmlBlock result
              :If 1=≡result
                  result←,ns ProcessInlineMarkUp⍣(flag1∧~flag2)⊣result
              :Else
                  'Embeded function returned invalid result'⎕SIGNAL 11/⍨(,1)≢∪≡¨,¨result
                  result←,ns{⍺ ProcessInlineMarkUp¨Nest ⍵}⍣(flag1∧~flag2)⊣result
              :EndIf
              'Called function returned an HTML block but does not stand on its own'⎕SIGNAL 11/⍨flag2∧0≠⍴html
              :If 0∊⍴html
                  html←(-ind[1])⌽result,html
              :Else
                  html←(-ind[1])⌽(⊃⍣(1<≡html)⊣result),html
              :EndIf
          :Until 0∊⍴ind←2↓ind
      :EndIf
    ∇

    ∇ r←FunctionCallResultIsHtmlBlock result
      :If ~0∊⍴'<pre\b[^>]*>'⎕S 0⊣⊃result
      :AndIf '</pre>'{⍺≡(-⍴⍺)↑⍵}⊃¯1↑result
          r←1
      :Else
          :If r←0=+/⊃¨⍴¨2↑¯1⌽result
              r←'<>'≡⊃¨1 ¯1↑¨result[2,¯1+⍴result]
          :EndIf
      :EndIf
    ∇

    ∇ {r}←ProcessParagraph ns;sa;para;hits;tag
      r←⍬
      ns.noOf←ScanForPara ns
      ns.noOf←1⌈ns.noOf
      para←CompilePara ns.noOf↑ns.markdown
      para←ns CheckOddNumberOfDoubleQuotes para'paragraph'
      sa←GetSpecialAttributes para
      para←sa DropSpecialAttributes⍣(0<⊃⍴sa)⊣para
      para←ns ProcessParagraph_ para
      :If ~0∊⍴para
          :If '<'=⊃⊃para                                ⍝ Is this...
          :AndIf ~0∊⍴hits←'<[^>].*?>'⎕S 0 1⊣dlb para    ⍝ ... an HTML block?
              hits←⊃hits
          :AndIf 0=⊃hits
              tag←(¯2+hits[2])↑1↓⊃para
          :AndIf ('</',tag,'>')≡(-3+⍴tag)↑(⍴para)⊃para
              ns.html,←Nest para
          :Else
              ns.html,←Nest sa{2=≡⍵:⍺∘∇¨⍵ ⋄ '<p',({0∊⍴⍵:'>' ⋄ ⍵,'>'}⍺),⍵,'</p>'}para
          :EndIf
      :EndIf
      ns←Drop ns
    ∇

      ProcessParagraph_←{
          ns←⍺
          para←⍵
          ns ProcessInlineMarkUp para
      }

    ∇ r←ScanForBlockQuotes ns;ns2;lc;b;max
      r←0
      ns2←⎕NS''
      lc←↑2↑¨ns.leadingChars        ⍝ Leading two chars
      max←¯1+ns.emptyLines⍳1        ⍝ An empty line marks the end
      ns2.(markdown markdownLC leadingChars emptyLines withoutBlanks lineNumbers)←max↑¨ns.(markdown markdownLC leadingChars emptyLines withoutBlanks lineNumbers)
      lc←max↑lc                     ⍝ Leading chars
      :While 0<ns2.noOf←1 ScanForPara ns2
          lc←ns2.noOf↓lc            ⍝ Leading chars
          :If 0∊⍴lc                 ⍝ No blockquotes...
          :OrIf '> '≢lc[1;]         ⍝ any more?
              r+←ns2.noOf
              :Leave                ⍝ No - we are done
          :EndIf
          ns2.noOf+←+/∧\lc∧.='> '   ⍝ Add them
          r+←ns2.noOf
          ns2←Drop ns2
      :EndWhile
    ∇

    ∇ noOf←{dontCheckForBlockQuote}ScanForPara ns;buff;bool
    ⍝ `noOf` : Number of lines, if any), the next paragraph will comprise.
      dontCheckForBlockQuote←{0<⎕NC ⍵:⍎⍵ ⋄ 0}'dontCheckForBlockQuote'
      :If 1≠noOf←+/∧\0=ns.emptyLines                                                          ⍝ How many lines until next empty line?
      :AndIf 1≠noOf←noOf⌊¯1+⊃'='IotaSetextHeader noOf↑¨ns.(withoutBlanks markdown emptyLines) ⍝ Header (= syntax)
      :AndIf 1≠noOf←noOf⌊¯1+⊃'-'IotaSetextHeader noOf↑¨ns.(withoutBlanks markdown emptyLines) ⍝ Header (- syntax)
      :AndIf 1≠noOf←+/∧\~(noOf↑⊃¨ns.leadingChars)∊'|#='                                       ⍝ HTML, header, tables?
      :AndIf 1≠noOf←+/∧\~{⊃⍴'^\s{0,3}[-+\*]\s'⎕S 0⊣⍵}¨noOf↑ns.markdown                        ⍝ bulleted list
      :AndIf 1≠noOf←+/∧\~{⊃⍴'^\s{0,3}[0-9]{1,9}[.)]'⎕S 0⊣⍵}¨noOf↑ns.markdown                  ⍝ Ordered lists?
      :AndIf 1≠noOf←+/∧\~{∨/({⍵⍴⍨3⌊⍴⍵}⍵~' ')∘≡¨'***' '---' '___'}¨noOf↑ns.markdown            ⍝ Horizontal rulers?
      :AndIf 1≠noOf←+/∧\~'```'∘{⍺≡(⍴⍺)↑⍵}¨noOf↑ns.markdown                                    ⍝ Code block (``` syntax)
      :AndIf 1≠noOf←+/∧\~'~~~'∘{⍺≡(⍴⍺)↑⍵}¨noOf↑ns.markdown                                    ⍝ Code block (~~~ syntax)
      :AndIf 1≠noOf←+/∧\{0∊⍴'
]*>' ''
          ns.html,←'</style>'DropTailAfterClosingTag ns.noOf↑ns.markdown
          ns←Drop ns
      :Else
          r←1
      :EndIf
    ∇

    ∇ r←ProcessHtmlBlockType_2 ns;start;e;md
    ⍝
    ⍝ ← is 0 when found and processed, otherwise 0.
      r←1
      :If 2<⍴ns.markdown
      :AndIf 1∊e←1↓ns.emptyLines
          md←(1+(1↓ns.emptyLines)⍳1)↑ns.markdown
          :If ∨/''⍷⊃¯1↑¯1↓md
                  ns.noOf←¯1+⍴md
                  ns.html,←'-->'DropTailAfterClosingTag 1↓ns.noOf↑ns.markdown
                  ns←Drop ns
                  r←0
              :Else
                  ns.noOf←⊃ns.emptyLines
              :EndIf
          :EndIf
      :EndIf
    ∇

    ∇ r←ProcessHtmlBlockType_3 ns;start;b;md
    ⍝ <? and ?>
    ⍝ ← is 0 when found and processed, otherwise 0.
      r←1
      :If 2<⍴ns.markdown
      :AndIf ⊃ns.emptyLines
      :AndIf '<?'{⍺≡(⍴⍺)↑⍵}2⊃ns.leadingChars
          :If 0<+/b←1↓ns.emptyLines
              md←(b⍳1)↑ns.markdown
          :AndIf 0<ns.noOf←DetectClosingTagBeforeEmptyLine ns.markdown'?>'
              ns.html,←'?>'DropTailAfterClosingTag 1↓ns.noOf↑ns.markdown
              ns←Drop ns
              r←0
          :EndIf
      :EndIf
    ∇

    ∇ r←ProcessHtmlBlockType_4 ns;start
    ⍝ "<!",⎕A
    ⍝ ← is 0 when found and processed, otherwise 0.
      r←1
      :If 2<⍴ns.markdown
      :AndIf 0∊⍴⊃ns.markdown
      :AndIf '<!'{⍺≡(⍴⍺)↑⍵}2⊃ns.leadingChars
      :AndIf 0<ns.noOf←DetectClosingTagBeforeEmptyLine ns.markdown'!>'
          ns.html,←'!>'DropTailAfterClosingTag 1↓ns.noOf↑ns.markdown
          ns←Drop ns
          r←0
      :EndIf
    ∇

    ∇ r←ProcessHtmlBlockType_5 ns;start
    ⍝ <![CDATA[ and ]]
    ⍝ ← is 0 when found and processed, otherwise 0.
      r←1
      :If 2<⍴ns.markdown
      :AndIf 0∊⍴⊃ns.markdown
      :AndIf '<![CDATA['{⍺≡(⍴⍺)↑⍵}2⊃ns.leadingChars
      :AndIf 0<ns.noOf←DetectClosingTagBeforeEmptyLine ns.markdown']]>'
          ns.html,←']]>'DropTailAfterClosingTag 1↓ns.noOf↑ns.markdown
          ns←Drop ns
          r←0
      :EndIf
    ∇

    ∇ r←ProcessHtmlBlockType_6 ns;start;t;f1;f2;buff;tag
    ⍝ Check for all possible (allowed and block) HTML5 tags.
    ⍝ This is different from others because it may start with a closing tag.
    ⍝ ← is 0 when found and processed, otherwise 0.
      r←1
      :If 1<⍴ns.markdown
      :AndIf ⊃ns.emptyLines
          :If f1←'</'≡2↑2⊃ns.leadingChars
          :OrIf '<'=⊃2⊃ns.leadingChars
              :If 1∊2↓ns.emptyLines             ⍝ Without a closing empty line it cannot be an HTML block
                  buff←dlb(f1+1)↓2⊃ns.markdownLC
                  buff←(buff⍳'>')↑buff          ⍝ Drop all behind the closing ">"
                  :If '/'∊buff
                      tag←⊂buff←buff~'<>/'
                  :Else
                      tag←⊂,(¯1+⌊/buff⍳AllWhiteSpaceChars,'/>')↑buff
                  :EndIf
                ⍝ Most frequently used tags first:
                  :If tag∊,¨'div' 'h1' 'h2' 'h3' 'h4' 'h5' 'h6' 'li' 'ol' 'p' 'table' 'tbody' 'td' 'tfoot' 'th' 'thead' 'tr' 'ul'
                  :OrIf tag∊,¨'address' 'article' 'aside' 'base' 'basefont' 'blockquote' 'body' 'caption' 'center' 'colgroup' 'col'
                  :OrIf tag∊,¨'dd' 'details' 'dialog' 'dir' 'dl' 'dt' 'fieldset' 'figcaption' 'figure' 'footer' 'form' 'frame'
                  :OrIf tag∊,¨'frameset' 'head' 'header' 'hr' 'html' 'iframe' 'legend' 'link' 'main' 'menu' 'menuitem' 'meta' 'nav'
                  :OrIf tag∊,¨'noframes' 'optgroup' 'option' 'param' 'section' 'source' 'summary' 'title' 'track' 'pre'
                      buff←(⍴↑tag)↓buff
                      :If 0∊⍴buff
                      :OrIf '>'=1⍴buff
                      :OrIf '/>'≡2⍴buff
                      :OrIf (1⍴buff)∊AllWhiteSpaceChars
                          ns.noOf←(1↓ns.emptyLines)⍳1
                          ns.html,←1↓ns.noOf↑ns.markdown        ⍝ Drop first because it is empty
                          ns←Drop ns
                          r←0
                      :EndIf
                  :EndIf
              :EndIf
          :EndIf
      :EndIf
    ∇

    ∇ r←ProcessHtmlBlockType_7 ns;start;cl
    ⍝ Free-style (= user defined) tags.
    ⍝ ← is 0 when found and processed, otherwise 0.
      r←1
      :If 1<⍴ns.markdown
      :AndIf ⊃ns.emptyLines
          cl←2⊃ns.markdownLC
          :If ~0∊⍴'<[a-z]*\b[^>]*>' '</[a-z][^>]*>'⎕S 0⊣cl     ⍝ Look for any tag
          :AndIf 0∊⍴'