3 ' Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
4 ' For licensing, see LICENSE.html or http://ckeditor.com/license
6 ' Shared variable for all instances ("static")
7 dim CKEDITOR_initComplete
8 dim CKEDITOR_returnedEvents
11 ' \brief CKEditor class that can be used to create editor
12 ' instances in ASP pages on server side.
13 ' @see http://ckeditor.com
17 ' editor = new CKEditor
18 ' editor.editor "editor1", "<p>Initial value.</p>", empty, empty
24 ' The version of %CKEditor.
28 ' A constant string unique for each release of %CKEditor.
32 ' URL to the %CKEditor installation directory (absolute or relative to document root).
33 ' If not set, CKEditor will try to guess it's path.
37 ' editor.basePath = "/ckeditor/"
42 ' A boolean variable indicating whether CKEditor has been initialized.
43 ' Set it to true only if you have already included
44 ' <script> tag loading ckeditor.js in your website.
48 ' Boolean variable indicating whether created code should be printed out or returned by a function.
50 ' Example 1: get the code creating %CKEditor instance and print it on a page with the "echo" function.
52 ' editor = new CKEditor
53 ' editor.returnOutput = true
54 ' code = editor.editor("editor1", "<p>Initial value.</p>", empty, empty)
55 ' response.write "<p>Editor 1:</p>"
61 ' A Dictionary with textarea attributes.
63 ' When %CKEditor is created with the editor() method, a HTML <textarea> element is created,
64 ' it will be displayed to anyone with JavaScript disabled or with incompatible browser.
65 public textareaAttributes
68 ' A string indicating the creation date of %CKEditor.
69 ' Do not change it unless you want to force browsers to not use previously cached version of %CKEditor.
73 ' A dictionary that holds the instance configuration.
74 private oInstanceConfig
77 ' A dictionary that holds the configuration for all the instances.
78 private oAllInstancesConfig
81 ' A dictionary that holds event listeners for the instance.
82 private oInstanceEvents
85 ' A dictionary that holds event listeners for all the instances.
86 private oAllInstancesEvents
89 ' A Dictionary that holds global event listeners (CKEDITOR object)
93 Private Sub Class_Initialize()
96 mTimeStamp = "B5GJ5GG"
98 Set oInstanceConfig = CreateObject("Scripting.Dictionary")
99 Set oAllInstancesConfig = CreateObject("Scripting.Dictionary")
101 Set oInstanceEvents = CreateObject("Scripting.Dictionary")
102 Set oAllInstancesEvents = CreateObject("Scripting.Dictionary")
103 Set oGlobalEvents = CreateObject("Scripting.Dictionary")
105 Set textareaAttributes = CreateObject("Scripting.Dictionary")
106 textareaAttributes.Add "rows", 8
107 textareaAttributes.Add "cols", 60
111 ' Creates a %CKEditor instance.
112 ' In incompatible browsers %CKEditor will downgrade to plain HTML <textarea> element.
114 ' @param name (string) Name of the %CKEditor instance (this will be also the "name" attribute of textarea element).
115 ' @param value (string) Initial value.
119 ' set editor = New CKEditor
120 ' editor.editor "field1", "<p>Initial value.</p>"
125 ' set editor = new CKEditor
126 ' set config = CreateObject("Scripting.Dictionary")
127 ' config.Add "toolbar", Array( _
128 ' Array( "Source", "-", "Bold", "Italic", "Underline", "Strike" ), _
129 ' Array( "Image", "Link", "Unlink", "Anchor" ) _
131 ' set events = CreateObject("Scripting.Dictionary")
132 ' events.Add "instanceReady", "function (evt) { alert('Loaded second editor: ' + evt.editor.name );}"
134 ' editor.editor "field1", "<p>Initial value.</p>", config, events
137 public function editor(name, value)
138 dim attr, out, js, customConfig, extraConfig
143 for each attribute in textareaAttributes
144 attr = attr & " " & attribute & "=""" & replace( textareaAttributes( attribute ), """", """ ) & """"
147 out = "<textarea name=""" & name & """" & attr & ">" & Server.HtmlEncode(value) & "</textarea>" & vbcrlf
149 if not(initialized) then
153 set customConfig = configSettings()
154 js = returnGlobalEvents()
156 extraConfig = (new JSON)( empty, customConfig, false )
157 if extraConfig<>"" then extraConfig = ", " & extraConfig
158 js = js & "CKEDITOR.replace('" & name & "'" & extraConfig & ");"
160 out = out & script(js)
162 if not(returnOutput) then
169 oInstanceConfig.RemoveAll
170 oInstanceEvents.RemoveAll
174 ' Replaces a <textarea> with a %CKEditor instance.
176 ' @param id (string) The id or name of textarea element.
178 ' Example 1: adding %CKEditor to <textarea name="article"></textarea> element:
180 ' set editor = New CKEditor
181 ' editor.replace "article"
184 public function replaceInstance(id)
185 dim out, js, customConfig, extraConfig
188 if not(initialized) then
192 set customConfig = configSettings()
193 js = returnGlobalEvents()
195 extraConfig = (new JSON)( empty, customConfig, false )
196 if extraConfig<>"" then extraConfig = ", " & extraConfig
197 js = js & "CKEDITOR.replace('" & id & "'" & extraConfig & ");"
199 out = out & script(js)
201 if not(returnOutput) then
206 replaceInstance = out
208 oInstanceConfig.RemoveAll
209 oInstanceEvents.RemoveAll
213 ' Replace all <textarea> elements available in the document with editor instances.
215 ' @param className (string) If set, replace all textareas with class className in the page.
217 ' Example 1: replace all <textarea> elements in the page.
219 ' editor = new CKEditor
220 ' editor.replaceAll empty
223 ' Example 2: replace all <textarea class="myClassName"> elements in the page.
225 ' editor = new CKEditor
226 ' editor.replaceAll 'myClassName'
229 function replaceAll(className)
230 dim out, js, customConfig
233 if not(initialized) then
237 set customConfig = configSettings()
238 js = returnGlobalEvents()
240 if (customConfig.Count=0) then
241 if (isEmpty(className)) then
242 js = js & "CKEDITOR.replaceAll();"
244 js = js & "CKEDITOR.replaceAll('" & className & "');"
247 js = js & "CKEDITOR.replaceAll( function(textarea, config) {\n"
248 if not(isEmpty(className)) then
249 js = js & " var classRegex = new RegExp('(?:^| )' + '" & className & "' + '(?:$| )');\n"
250 js = js & " if (!classRegex.test(textarea.className))\n"
251 js = js & " return false;\n"
253 js = js & " CKEDITOR.tools.extend(config, " & (new JSON)( empty, customConfig, false ) & ", true);"
257 out = out & script(js)
259 if not(returnOutput) then
266 oInstanceConfig.RemoveAll
267 oInstanceEvents.RemoveAll
272 ' A Dictionary that holds the %CKEditor configuration for all instances
273 ' For the list of available options, see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html
277 ' editor.config("height") = 400
278 ' // Use @@ at the beggining of a string to ouput it without surrounding quotes.
279 ' editor.config("width") = "@@screen.width * 0.8"
281 Public Property Let Config( configKey, configValue )
282 oAllInstancesConfig.Add configKey, configValue
286 ' Configuration options for the next instance
288 Public Property Let instanceConfig( configKey, configValue )
289 oInstanceConfig.Add configKey, configValue
293 ' Adds event listener.
294 ' Events are fired by %CKEditor in various situations.
296 ' @param eventName (string) Event name.
297 ' @param javascriptCode (string) Javascript anonymous function or function name.
301 ' editor.addEventHandler "instanceReady", "function (ev) { " & _
302 ' " alert('Loaded: ' + ev.editor.name); " & _
306 public sub addEventHandler(eventName, javascriptCode)
307 if not(oAllInstancesEvents.Exists( eventName ) ) then
308 oAllInstancesEvents.Add eventName, Array()
312 listeners = oAllInstancesEvents( eventName )
313 size = ubound(listeners) + 1
314 redim preserve listeners(size)
315 listeners(size) = javascriptCode
317 oAllInstancesEvents( eventName ) = listeners
318 ' '' Avoid duplicates. fixme...
319 ' if (!in_array($javascriptCode, $this->_events[$event])) {
320 ' $this->_events[$event][] = $javascriptCode;
325 ' Clear registered event handlers.
326 ' Note: this function will have no effect on already created editor instances.
328 ' @param eventName (string) Event name, if set to 'empty' all event handlers will be removed.
330 public sub clearEventHandlers( eventName )
331 if not(isEmpty( eventName )) then
332 oAllInstancesEvents.Remove eventName
334 oAllInstancesEvents.RemoveAll
340 ' Adds event listener only for the next instance.
341 ' Events are fired by %CKEditor in various situations.
343 ' @param eventName (string) Event name.
344 ' @param javascriptCode (string) Javascript anonymous function or function name.
348 ' editor.addInstanceEventHandler "instanceReady", "function (ev) { " & _
349 ' " alert('Loaded: ' + ev.editor.name); " & _
353 public sub addInstanceEventHandler(eventName, javascriptCode)
354 if not(oInstanceEvents.Exists( eventName ) ) then
355 oInstanceEvents.Add eventName, Array()
359 listeners = oInstanceEvents( eventName )
360 size = ubound(listeners) + 1
361 redim preserve listeners(size)
362 listeners(size) = javascriptCode
364 oInstanceEvents( eventName ) = listeners
365 ' '' Avoid duplicates. fixme...
366 ' if (!in_array($javascriptCode, $this->_events[$event])) {
367 ' $this->_events[$event][] = $javascriptCode;
372 ' Clear registered event handlers.
373 ' Note: this function will have no effect on already created editor instances.
375 ' @param eventName (string) Event name, if set to 'empty' all event handlers will be removed.
377 public sub clearInstanceEventHandlers( eventName )
378 if not(isEmpty( eventName )) then
379 oInstanceEvents.Remove eventName
381 oInstanceEvents.RemoveAll
386 ' Adds global event listener.
388 ' @param event (string) Event name.
389 ' @param javascriptCode (string) Javascript anonymous function or function name.
393 ' editor.addGlobalEventHandler "dialogDefinition", "function (ev) { " & _
394 ' " alert('Loading dialog: ' + ev.data.name); " & _
398 public sub addGlobalEventHandler( eventName, javascriptCode)
399 if not(oGlobalEvents.Exists( eventName ) ) then
400 oGlobalEvents.Add eventName, Array()
404 listeners = oGlobalEvents( eventName )
405 size = ubound(listeners) + 1
406 redim preserve listeners(size)
407 listeners(size) = javascriptCode
409 oGlobalEvents( eventName ) = listeners
411 ' // Avoid duplicates.
412 ' if (!in_array($javascriptCode, $this->_globalEvents[$event])) {
413 ' $this->_globalEvents[$event][] = $javascriptCode;
418 ' Clear registered global event handlers.
419 ' Note: this function will have no effect if the event handler has been already printed/returned.
421 ' @param eventName (string) Event name, if set to 'empty' all event handlers will be removed .
423 public sub clearGlobalEventHandlers( eventName )
424 if not(isEmpty( eventName )) then
425 oGlobalEvents.Remove eventName
427 oGlobalEvents.RemoveAll
432 ' Prints javascript code.
436 private function script(js)
437 script = "<script type=""text/javascript"">" & _
438 "//<![CDATA[" & vbcrlf & _
445 ' Returns the configuration array (global and instance specific settings are merged into one array).
447 ' @param instanceConfig (Dictionary) The specific configurations to apply to editor instance.
448 ' @param instanceEvents (Dictionary) Event listeners for editor instance.
450 private function configSettings()
451 dim mergedConfig, mergedEvents
452 set mergedConfig = cloneDictionary(oAllInstancesConfig)
453 set mergedEvents = cloneDictionary(oAllInstancesEvents)
455 if not(isEmpty(oInstanceConfig)) then
456 set mergedConfig = mergeDictionary(mergedConfig, oInstanceConfig)
459 if not(isEmpty(oInstanceEvents)) then
460 for each eventName in oInstanceEvents
461 code = oInstanceEvents( eventName )
463 if not(mergedEvents.Exists( eventName)) then
464 mergedEvents.Add eventName, code
468 listeners = mergedEvents( eventName )
469 size = ubound(listeners)
470 if isArray( code ) then
471 addedCount = ubound(code)
472 redim preserve listeners( size + addedCount + 1 )
473 for i = 0 to addedCount
474 listeners(size + i + 1) = code (i)
478 redim preserve listeners(size)
479 listeners(size) = code
482 mergedEvents( eventName ) = listeners
488 dim i, eventName, handlers, configON, ub, code
490 if mergedEvents.Count>0 then
491 if mergedConfig.Exists( "on" ) then
492 set configON = mergedConfig.items( "on" )
494 set configON = CreateObject("Scripting.Dictionary")
495 mergedConfig.Add "on", configOn
498 for each eventName in mergedEvents
499 handlers = mergedEvents( eventName )
502 if isArray(handlers) then
503 uB = ubound(handlers)
507 code = "function (ev) {"
509 code = code & "(" & handlers(i) & ")(ev);"
516 ' Using @@ at the beggining to signal JSON that we don't want this quoted.
517 configON.Add eventName, "@@" & code
520 ' set mergedConfig.Item("on") = configOn
523 set configSettings = mergedConfig
527 ' Returns a copy of a scripting.dictionary object
529 private function cloneDictionary( base )
532 Set newOne = CreateObject("Scripting.Dictionary")
533 for each tmpKey in base
534 newOne.Add tmpKey , base( tmpKey )
537 set cloneDictionary = newOne
541 ' Combines two scripting.dictionary objects
542 ' The base object isn't modified, and extra gets all the properties in base
544 private function mergeDictionary(base, extra)
547 for each tmpKey in base
548 if not(extra.Exists( tmpKey )) then
549 extra.Add tmpKey, base( tmpKey )
553 set mergeDictionary = extra
557 ' Return global event handlers.
559 private function returnGlobalEvents()
560 dim out, eventName, handlers
561 dim handlersForEvent, handler, code, i
564 if (isempty(CKEDITOR_returnedEvents)) then
565 set CKEDITOR_returnedEvents = CreateObject("Scripting.Dictionary")
568 for each eventName in oGlobalEvents
569 handlers = oGlobalEvents( eventName )
571 if not(CKEDITOR_returnedEvents.Exists(eventName)) then
572 CKEDITOR_returnedEvents.Add eventName, CreateObject("Scripting.Dictionary")
575 set handlersForEvent = CKEDITOR_returnedEvents.Item( eventName )
577 ' handlersForEvent is another dictionary
578 ' and handlers is an array
580 for i = 0 to ubound(handlers)
583 ' Return only new events
584 if not(handlersForEvent.Exists( code )) then
585 if (out <> "") then out = out & vbcrlf
586 out = out & "CKEDITOR.on('" & eventName & "', " & code & ");"
587 handlersForEvent.Add code, code
592 returnGlobalEvents = out
596 ' Initializes CKEditor (executed only once).
598 private function init()
599 dim out, args, path, extraCode, file
602 if (CKEDITOR_initComplete) then
607 if (initialized) then
608 CKEDITOR_initComplete = true
614 path = ckeditorPath()
616 if (timestamp <> "") and (timestamp <> "%" & "TIMESTAMP%") then
617 args = "?t=" & timestamp
620 ' Skip relative paths...
621 if (instr(path, "..") <> 0) then
622 out = out & script("window.CKEDITOR_BASEPATH='" & path & "';")
625 out = out & "<scr" & "ipt type=""text/javascript"" src=""" & path & ckeditorFileName() & args & """></scr" & "ipt>" & vbcrlf
628 if (timestamp <> mTimeStamp) then
629 extraCode = extraCode & "CKEDITOR.timestamp = '" & timestamp & "';"
631 if (extraCode <> "") then
632 out = out & script(extraCode)
635 CKEDITOR_initComplete = true
641 private function ckeditorFileName()
642 ckeditorFileName = "ckeditor.js"
646 ' Return path to ckeditor.js.
648 private function ckeditorPath()
649 if (basePath <> "") then
650 ckeditorPath = basePath
652 ' In classic ASP we can't get the location of this included script
653 ckeditorPath = "/ckeditor/"
656 ' Try to check if that folder contains the CKEditor files:
657 ' If it's a full URL avoid checking it as it might point to an external server.
658 if (instr(ckeditorPath, "://") <> 0) then exit function
660 dim filename, oFSO, exists
661 filename = server.mapPath(basePath & ckeditorFileName())
662 set oFSO = Server.CreateObject("Scripting.FileSystemObject")
663 exists = oFSO.FileExists(filename)
668 response.write "<h1>CKEditor path validation failed</h1>"
669 response.write "<p>The path "" & ckeditorPath & "" doesn't include the CKEditor main file (" & ckeditorFileName() & ")</p>"
670 response.write "<p>Please, verify that you have set it correctly and/or adjust the 'basePath' property</p>"
671 response.write "<p>Checked for physical file: "" & filename & ""</p>"
680 ' URL: http://www.webdevbros.net/2007/04/26/generate-json-from-asp-datatypes/
681 '**************************************************************************************************************
683 '' @CREATOR: Michal Gabrukiewicz (gabru at grafix.at), Michael Rebec
684 '' @CONTRIBUTORS: - Cliff Pruitt (opensource at crayoncowboy.com)
685 '' - Sylvain Lafontaine
688 '' @CREATEDON: 2007-04-26 12:46
689 '' @CDESCRIPTION: Comes up with functionality for JSON (http://json.org) to use within ASP.
690 '' Correct escaping of characters, generating JSON Grammer out of ASP datatypes and structures
691 '' Some examples (all use the <em>toJSON()</em> method but as it is the class' default method it can be left out):
695 '' output = (new JSON)("myNum", 2, false)
696 '' 'generates {"myNum": 2}
698 '' 'array with different datatypes
699 '' output = (new JSON)("anArray", array(2, "x", null), true)
700 '' 'generates "anArray": [2, "x", null]
701 '' '(note: the last parameter was true, thus no surrounding brackets in the result)
705 '' @OPTIONEXPLICIT: yes
708 '**************************************************************************************************************
712 private output, innerCall
714 '**********************************************************************************************************
716 '**********************************************************************************************************
717 public sub class_initialize()
721 '******************************************************************************************
722 '' @SDESCRIPTION: STATIC! takes a given string and makes it JSON valid
723 '' @DESCRIPTION: all characters which needs to be escaped are beeing replaced by their
724 '' unicode representation according to the
725 '' RFC4627#2.5 - http://www.ietf.org/rfc/rfc4627.txt?number=4627
726 '' @PARAM: val [string]: value which should be escaped
727 '' @RETURN: [string] JSON valid string
728 '******************************************************************************************
729 public function escape(val)
730 dim cDoubleQuote, cRevSolidus, cSolidus
735 for i = 1 to (len(val))
736 currentDigit = mid(val, i, 1)
737 if ascw(currentDigit) > &h00 and ascw(currentDigit) < &h1F then
738 currentDigit = escapequence(currentDigit)
739 elseif ascw(currentDigit) >= &hC280 and ascw(currentDigit) <= &hC2BF then
740 currentDigit = "\u00" + right(padLeft(hex(ascw(currentDigit) - &hC200), 2, 0), 2)
741 elseif ascw(currentDigit) >= &hC380 and ascw(currentDigit) <= &hC3BF then
742 currentDigit = "\u00" + right(padLeft(hex(ascw(currentDigit) - &hC2C0), 2, 0), 2)
744 select case ascw(currentDigit)
745 case cDoubleQuote: currentDigit = escapequence(currentDigit)
746 case cRevSolidus: currentDigit = escapequence(currentDigit)
747 case cSolidus: currentDigit = escapequence(currentDigit)
750 escape = escape & currentDigit
754 '******************************************************************************************************************
755 '' @SDESCRIPTION: generates a representation of a name value pair in JSON grammer
756 '' @DESCRIPTION: It generates a name value pair which is represented as <em>{"name": value}</em> in JSON.
757 '' the generation is fully recursive. Thus the value can also be a complex datatype (array in dictionary, etc.) e.g.
761 '' j.toJSON "n", array(RS, dict, false), false
762 '' j.toJSON "n", array(array(), 2, true), false
765 '' @PARAM: name [string]: name of the value (accessible with javascript afterwards). leave empty to get just the value
766 '' @PARAM: val [variant], [int], [float], [array], [object], [dictionary]: value which needs
767 '' to be generated. Conversation of the data types is as follows:<br>
768 '' - <strong>ASP datatype -> JavaScript datatype</strong>
769 '' - NOTHING, NULL -> null
770 '' - INT, DOUBLE -> number
771 '' - STRING -> string
774 '' - DICTIONARY -> Represents it as name value pairs. Each key is accessible as property afterwards. json will look like <code>"name": {"key1": "some value", "key2": "other value"}</code>
775 '' - <em>multidimensional array</em> -> Generates a 1-dimensional array (flat) with all values of the multidimensional array
776 '' - <em>request</em> object -> every property and collection (cookies, form, querystring, etc) of the asp request object is exposed as an item of a dictionary. Property names are <strong>lowercase</strong>. e.g. <em>servervariables</em>.
777 '' - OBJECT -> name of the type (if unknown type) or all its properties (if class implements <em>reflect()</em> method)
778 '' Implement a <strong>reflect()</strong> function if you want your custom classes to be recognized. The function must return
779 '' a dictionary where the key holds the property name and the value its value. Example of a reflect function within a User class which has firstname and lastname properties
782 '' function reflect()
783 '' . set reflect = server.createObject("scripting.dictionary")
784 '' . reflect.add "firstname", firstname
785 '' . reflect.add "lastname", lastname
789 '' Example of how to generate a JSON representation of the asp request object and access the <em>HTTP_HOST</em> server variable in JavaScript:
791 '' <script>alert(<%= (new JSON)(empty, request, false) % >.servervariables.HTTP_HOST);</script>
793 '' @PARAM: nested [bool]: indicates if the name value pair is already nested within another? if yes then the <em>{}</em> are left out.
794 '' @RETURN: [string] returns a JSON representation of the given name value pair
795 '******************************************************************************************************************
796 public default function toJSON(name, val, nested)
797 if not nested and not isEmpty(name) then write("{")
798 if not isEmpty(name) then write("""" & escape(name) & """: ")
800 if not nested and not isEmpty(name) then write("}")
803 if innerCall = 0 then newGeneration()
806 '******************************************************************************************************************
808 '******************************************************************************************************************
809 private function generateValue(val)
812 elseif isArray(val) then
814 elseif isObject(val) then
815 dim tName : tName = typename(val)
816 if val is nothing then
818 elseif tName = "Dictionary" or tName = "IRequestDictionary" then
819 generateDictionary(val)
820 elseif tName = "IRequest" then
821 set req = server.createObject("scripting.dictionary")
822 req.add "clientcertificate", val.ClientCertificate
823 req.add "cookies", val.cookies
824 req.add "form", val.form
825 req.add "querystring", val.queryString
826 req.add "servervariables", val.serverVariables
827 req.add "totalbytes", val.totalBytes
828 generateDictionary(req)
829 elseif tName = "IStringList" then
830 if val.count = 1 then
831 toJSON empty, val(1), true
841 varTyp = varType(val)
843 if val then write("true") else write("false")
845 elseif varTyp = 2 or varTyp = 3 or varTyp = 17 or varTyp = 19 then
847 'single, double, currency
848 elseif varTyp = 4 or varTyp = 5 or varTyp = 6 or varTyp = 14 then
849 write(replace(cDbl(val), ",", "."))
851 ' Using @@ at the beggining to signal JSON that we don't want this quoted.
852 if left(val, 2) = "@@" then
853 write( mid( val, 3 ) )
855 write("""" & escape(val & "") & """")
859 generateValue = output
862 '******************************************************************************************************************
864 '******************************************************************************************************************
865 private sub generateArray(val)
869 'the for each allows us to support also multi dimensional arrays
871 if i > 0 then write(",")
878 '******************************************************************************************************************
879 '* generateDictionary
880 '******************************************************************************************************************
881 private sub generateDictionary(val)
882 innerCall = innerCall + 1
883 if val.count = 0 then
884 toJSON empty, null, true
891 if i > 0 then write(",")
892 toJSON key, val(key), true
896 innerCall = innerCall - 1
899 '******************************************************************************************************************
901 '******************************************************************************************************************
902 private sub generateObject(val)
905 set props = val.reflect()
908 innerCall = innerCall + 1
909 toJSON empty, props, true
910 innerCall = innerCall - 1
913 write("""" & escape(typename(val)) & """")
917 '******************************************************************************************************************
919 '******************************************************************************************************************
920 private sub newGeneration()
925 '******************************************************************************************
927 '******************************************************************************************
928 private function escapequence(digit)
929 escapequence = "\u00" + right(padLeft(hex(ascw(digit)), 2, 0), 2)
932 '******************************************************************************************
934 '******************************************************************************************
935 private function padLeft(value, totalLength, paddingChar)
936 padLeft = right(clone(paddingChar, totalLength) & value, totalLength)
939 '******************************************************************************************
941 '******************************************************************************************
942 private function clone(byVal str, n)
944 for i = 1 to n : clone = clone & str : next
947 '******************************************************************************************
949 '******************************************************************************************
950 private sub write(val)
951 output = output & val