There are times when one wants to insert data to be used by JavaScript functions on the webpage from the code behind. This is quite straightforward if the data is plain
text without escape characters, but slightly more complex if there are escape sequences and special symbols in the text. The difficulty comes from the fact that there is
no bijective conversion between the encoding and decoding functions provided by JavaScript and the .Net environments. The escape()/unsecape() functions of JavaScript have
no .Net equivalent at all and the other JavaScript encoding functions such as encodeURI()/decodeURI() and encodeURIComponent()/decodeURIComponent() cannot be coupled with
the .Net System.Text.Encode functions since they behave equivalently on only a limited subset of the typically used escape or special characters, and thus I could not use
these functions either.
The solution was to create my own encoding. In the particular case when the problem came to me, I needed to embed text parameter to a JavaScript function call which would be
called when a link was pressed. The text parameter was in fact YouTube video embedding string which contains ", <, > and other escape and special characters requiring
attention. Armed with some old assembly language skills, I created a simple and coherent encoding and resolved the problem as shown below.
On the server side I used typical C# code to format the link text in a dynamically created page and table in it. In other cases one might have used the Register-Script API or
another more suitable approach to embed the JavaScript invoking code into the webpage. In this piece of code the
string EncodeA2P( string ) is the encoding function which we will explain shortly. EncodeA2P takes the
escape and special symbols containing string and returns it as an encoded string with no escape or special characters in it, and thus it will not interfere with the page and
JavaScript processing on the client side.
tcVideo.Text = string.Format( "<a href='#' onclick='javascript:SetVideo( \"{0}\" );'>{1}</a>",
EncodeA2P( strYouTubeEmbedString_FromDatabase ),
strVideoTitle_FromDatabase);
public static string EncodeA2P( string strToEncode )
{
return( EncodeA2P( System.Text.Encoding.UTF8.GetBytes( strToEncode ) ) );
}
public static string EncodeA2P( byte[] strDataToEncode )
{
int Length = strDataToEncode.GetLength( 0 );
char[] Destination = new char[2*Length];
for( int Index = 0; Index < Length; Index++ )
{
byte b1Byte = strDataToEncode[Index];
char b1MSHB = (char)( b1Byte >> 0x04 );
char b1LSHB = (char)( b1Byte & 0x0F );
Destination[2 * Index] = (char)('A' + b1MSHB);
Destination[2 * Index + 1] = (char)('A' + b1LSHB);
}
return( new string( Destination ) );
}
The string EncodeA2P( string ) function takes a string with any possible
character in it and returns an ASCII-A2P encoded string. To the best of my knowledge, there is no such well known definition so I will define it now. The name ASCII-A2P stands for
American Standard Code for Information Interchange - A to P or ASCII from A to P.
Definition: ASCII-A2P is encoding in which every half byte from
the encoded string is represented as an ASCII character, in a hexadecimal base counting system, where the zero is represented by 'A' and 15 by 'P'.
I could have used the ASCII characters of the respective hexadecimal numbers in the encoded string but that would make the conversion slightly more complicated
absolutely unnecessary. The string EncodeA2P( string ) function takes the data of the string
passed to it as a byte array, and passes it to its override string EncodeA2P( byte[] ) which
does the actual conversion. This embedding of calls is done for design considerations, explained at the end of the article. The string
EncodeA2P( byte[] ) takes the byte array holding the data to be encoded, in my case the data of YouTube video embedding strings and
returns ASCII-A2P encoded string. For those who are not familiar with binary arithmetic, I will explain the function in more detail. A byte in hexadecimal base counting
system looks like this: 0xYY where Y is a number from 0 to 9, A (=ten), B (=eleven), C, D, E or F (=fifteen), thus a byte covers all numbers from 0x00 to 0xFF (0 to 255 in
decimal). The way in which the string EncodeA2P( byte[] ) function works is that it takes each byte
from the array passed to it and splits it into two halves: left (more significant) and right (less significant). Each of these half bytes is actually a number on its own
with value from 0 to 0x0F (0 to 15 decimal). Then each of these numbers are converted to ASCII symbols by adding 'A' to them. Thus every 0 becomes 'A', 1 becomes 'B', ...
0x0E (14 decimal) becomes 'O' and 0x0F (15 decimal) becomes 'P'. Then the function concatenates these characters in a straight order to produce the new string which is
returned as the encoded representation of the byte array passed to the function.
On the client side, I created the inverse function to decode the ASCII-A2P encoded data into the original string, which is placed in the script section of the head
of the page. Note that the SetVideo( string ) function which calls the DecodeA2P( ASCII-A2P )
function is the one referenced in the dynamic link building which we mentioned in the beginning.
function DecodeA2P( strEncodedText )
{
if( 1 == (1 & strEncodedText.length) )
{
throw "Not an ASCII-A2P encoding 1.";
}
var arrayResult = new Array();
strEncodedText = strEncodedText.toUpperCase();
for( var Index = 0; Index < strEncodedText.length; Index += 2 )
{
var sb1MSB = strEncodedText.charCodeAt( Index ) - 65;
var sb1LSB = strEncodedText.charCodeAt( Index + 1 ) - 65;
if( (15 < sb1LSB) || (15 < sb1MSB) )
{
throw "Not an ASCII-A2P encoding 2.";
}
arrayResult[Index/2] = (sb1MSB << 4) | sb1LSB;
}
return( arrayResult );
}
function DecodeA2PAsString( strEncodedText )
{
var strResult = "";
var arrDecoded = DecodeA2P( strEncodedText );
for( var Index = 0; Index < arrDecoded.length; Index++ )
{
strResult = strResult + String.fromCharCode( arrDecoded[Index] );
}
return( strResult );
}
function SetVideo( varURL )
{
parent.document.getElementById( "cellTV" ).innerHTML = DecodeA2PAsString( varURL );
}
The SetVideo( string ) function gets called when the link in which its reference is embedded is clicked, and the encoded video
embedding string is passed to it. SetVideo( string ) calls DecodeA2PAsString( ASCII-A2P ) to decode
the video embedding code (in my particular task) and injects the decoded YouTube embedding code into the target cell of a table where the YouTube video player will
be displayed. DecodeA2PAsString( ASCII-A2P ) in turn calls the DecodeA2P( ASCII-A2P ) which
actually does the work.
The DecodeA2P( ASCII-A2P ) function works as follows: first it tests that the passed string has an even length. Then it sets the string
to upper case, just in case someone/something changed the case of the ASCII-A2P encoded data. It then takes every two characters from the encoded data in sequences
of pairs and subtracts 65 ('A') from each number. Thus any 'A' becomes zero, 'B' becomes 1, ... 'O' becomes 0x0E (14 decimal) and 'P' becomes 0x0F (15 decimal). Then
the left symbol, which was the most important half of the original number, is shifted 4 bits to the left and merged with the less significant part using a bitwise OR.
Thus DecodeA2P( ASCII-A2P ) returns an array of decoded bytes which is the same as the array passed to string
EncodeA2P( byte[] ) on the server side. DecodeA2PAsString( ASCII-A2P ) converts that array of bytes to a string
concatenated in a straight order to result in the original string (passed to string EncodeA2P( string ) on
the server side), which is finally returned to the SetVideo( string ) function to be inserted into the table cell.
In conclusion I will make a brief analysis of the results and the ASCII-A2P encoding. The method solves the problem
beautifully as I am now able to encode any string and pass it with no fear of character being incorrectly decoded. The ASCII-A2P encoding has some advantages and
some drawbacks. ASCII-A2P is always twice the size of the original string. This is good from a predictability point of view, but in most cases would probably yield
in a larger encoded string than EncodeURL; however in cases where there are many escape/special characters ASCII-A2P may even produce smaller encoded data than EncodeUrl.
A disadvantage of ASCII-A2P encoding is that generally it is unreadable by humans while the EncodeURL can be read by the developer "AS IS", for example, for debugging purposes.
Another advantage of ASCII-A2P is that unlike EncodeURL, it encodes the data homogenously: all characters are handled in a consistent way. Yet another advantage is that
ASCII-A2P is error aware, as the encoded symbols are in a range, thus any symbol outside of that range is an indication of an error. Further, since ASCII-A2P encoded
data always has even length, if an encoded data has odd length then it necessarily contains an error.
The string EncodeA2P( string ) and its inverse DecodeA2P( ASCII-A2P ) are the functions that I needed
to solve my problem. However, notice that the ASCII-A2P encoding actually works on binary data, which is much more generic and powerful than working on strings only.
For this reason I created the binary overrides of the ASCII-A2P encoding and decoding
(string EncodeA2P( byte[] ) and
byte-array DecodeA2P( ASCII-A2P )) as the central functions, which I wrapped with string interfacing
overrides which I needed for this particular solution. In this manner I achieve optimal design and reuse of the code.
|