Creating JavaScript Extensions for Dreamweaver


To create JavaScript Extensions for Dreamweaver, you will need an understanding of at least the C programming language (C++ is optional) and a development environment that can create shared libraries for your target platforms, such as Microsoft Visual C++ or Metrowerks CodeWarrior. You can use other development environments to build JSExtensions; however, Visual C++ and CodeWarrior are the most common. Consult the documentation for your particular development environment for information on how to build shared libraries (usually called Dynamic Link Libraries, or DLLs, on Windows ).

To create JavaScript extensions for Dreamweaver, you generally follow these steps:

  1. Create the project file for the extension to build a shared library.

  2. Create the main source file and write some common initialization code that all JSExtensions use.

  3. Define the methods that will make up the internal logic of the extension.

  4. Expose these internal methods to the JavaScript layer.

  5. Test and debug the extension.

In addition to these steps, it will be necessary to convert data types from the JavaScript environment to the C environment, report errors that occur within your extension to the user , and sometimes even call back into the JavaScript environment from within the extension's C code, as seen in Figure 10.2. The Dreamweaver C extensibility API provides methods for doing all of these things.

Figure 10.2. Calls to the DLL from within a Dreamweaver begin and end in the JavaScript-based extension.

graphics/10fig02.gif

Setting Up the Project

JSExtension projects must be created as shared libraries (DLLs on Windows). Your particular development environment will have all the necessary information on how to do this; information presented in this book is intended for users of Microsoft Visual C++ 6.0 and Metrowerks CodeWarrior Pro 5.

Building a Shared Library in Windows

If you are using Visual C++ 6.0, building a DLL is easy.

  1. From the File menu, select New, and then select the Projects tab in the dialog box that appears.

    You can choose to create a plain C++ DLL or use the MFC AppWizard to create an MFC DLL (see the next section about this process).

  2. Select the type of DLL you want to create in the list of project types.

  3. Use the Location field to type or browse to a path to the directory where you want the project to be created.

  4. Supply a name for the project in the Project Name field and click OK.

  5. Select the type of DLL you want to create from the next dialog box. Click Finish.

    Usually, you'll select either An Empty DLL Project or A Simple DLL Project. Select A DLL That Exports Some Symbols if your DLL will export more than just the Dreamweaver C extensibility symbols.

NOTE

The standard header file mm_jsapi.h automatically exports the necessary functions to call your extension's initialization code. You do not need to add a .DEF file to your DLL unless you plan to export functions other than the Dreamweaver initialization entry point.


You are now ready to add source files to your Windows extension project.

Building JSExtensions as MFC DLLs

You can use the MFC AppWizard to create extensions that use the Microsoft Foundation Classes, but you will need to do some extra work in your methods that you expose to the JavaScript environment.

Specifically , any of your exported methods that call into the MFC class library must include the macro AFX_MANAGE_STATE as the first line of the method. This ensures that MFC can correctly load any resources that MFC uses from the MFC resources instead of your extension's resources. MFC manages an internal state context that keeps track of where to load resources from, and this macro makes sure that the contexts don't get mixed up. More information on this topic is available in Microsoft Tech Notes 58 and 35, available at the MSDN Library (msdn.microsoft.com/library).

Building a Shared Library in Macintosh

Building shared libraries on the Macintosh using CodeWarrior is rather straightforward. All of the settings that need to be changed can be accessed under the Project Settings dialog box, accessed via the "{Project Name} Settings" item in the Edit menu (where the {Project Name} is replaced by the name of your project).

NOTE

These instructions are provided for users of CodeWarrior Pro 5, although most recent CodeWarrior versions are similar. See your version's documentation for more details.


  1. In CodeWarrior, choose New from the File menu.

  2. In the Project tab, select MacOS C/C++ Project Stationery. Enter a name for the project in the Project Name edit field, and select a folder to store your project in. (If you type the name of a nonexistent folder, the folder will be created for you.) Click OK.

    A new project will be created and opened for you, containing some sample source code and link libraries that you will need to link your extension to the Mac system. The project will be divided into Debug and Release build sections.

  3. In the new project window, remove any files from the Sources and Resources sections that CodeWarrior automatically added.

  4. Under the Edit menu, select the menu item named {Project Name} Settings, where {Project Name} is the name of your project.

  5. In the Target Settings tab, set the Linker selection to MacOS PPC Linker.

  6. Set the Output Directory to point to the folder where you want the finished shared library to be placed when it's built.

  7. Under the Runtime Settings tab, set the Host Application for Libraries and Code Resources to point to the Dreamweaver executable.

  8. Under the PPC Target tab, set the Project setting to Shared Library. Name the shared library the same name as your project. Set the Creator setting to ???? and the Type field to shlb . This will provide a default icon for your library in the Finder. If you want to use a custom icon, you can set the Creator field to your Apple Developer Creator ID.

  9. In the PPC PEF tab, set the Export menu to use #pragma. This will automatically export the Dreamweaver initialization point for you.

TIP

You can set this path to point directly to the JSExtensions directory of your Dreamweaver installation; this will save you the step of having to copy it there manually after each build.


You are now ready to add source files to your Macintosh extension project.

Basic Structure of the Extension

Consider Listing 10-1, a code listing for a JSExtension that adds two numbers and returns a result:

Listing 10-1 AddNumbers.cpp (10_addnumbers.cpp)
 1:  // AddNumbers.cpp : Takes two numbers and adds them together, then  2:  // returns the result back to the JavaScript world.  3:  4:  #ifdef _WIN32   // Windows  5:  #include "windows.h"  6:  #else           // Mac  7:  #include <MacHeaders.h>  8:  #endif  9:  10: #ifdef __cplusplus  11: extern "C" {  12: #endif  13: #include "mm_jsapi.h"  14: #ifdef __cplusplus  15: }  16: #endif  17:  18: MM_STATE  19:  20: void  21: MM_Init(void)  22: {  23:   JS_DefineFunction("addTwoNumbers", addTwoNumbers, 2);  24: }  25:  26: JSBool addTwoNumbers (JSContext* cx, JSObject* obj, unsigned  int argc, jsval* jsArgv, jsval* rval)  27: {  28:   int firstNumber;  29:   int secondNumber;  30:   int result;   31:  32:   // Get the arguments from JavaScript and convert them to C  33:   // data types.  34:   JS_ValueToInteger(cx, jsArgv[0], &firstNumber);  35:   JS_ValueToInteger(cx, jsArgv[1], &secondNumber);  36:  37:   // Add them together  38:   result = firstNumber + secondNumber;  39:  40:   // Convert the result to a JavaScript value and return it  41:   *rval = JS_IntegerToValue(result);  42:  43:   return JS_TRUE;  44: } 

This C extension is divided into three distinct sections: including the necessary header files, writing the initialization code, and implementing the extension's functionality.

Including Platform-Specific Headers

Lines 48 of Listing 10-1 include some platform-specific header files depending on the platform the extension is being compiled for:

 4:  #ifdef _WIN32   // Windows  5:  #include "windows.h"  6:  #else           // Mac  7:  #include <MacHeaders.h>  8:  #endif 

These header files contain all the necessary definitions needed to call the built-in functions for the Mac and Windows operating systems.

Including mm_jsapi.h

Line 13 is the core of the next stage as it includes the file "mm_jsapi.h" .All JSExtensions must include this header file. The mm_jsapi.h file includes definitions that are needed for your JSExtension code to communicate with Dreamweaver and perform other tasks related to converting data types and initializing your extension.

graphics/10icon01.gif

It is surrounded by an extern "C" declaration block to prevent the C++ compiler from mangling the object code names of the included functions. You must do this if you write your initialization code in a C++ file instead of a plain C file.

NOTE

You can find the latest version of mm_jsapi.h on the Dreamweaver Exchange.


Writing the Initialization Code

Lines 1824 show the common initialization code that all JSExtensions must call. First, on line 18, the macro MM_STATE is declared, which expands into a function that sets up some initialization parameters used by the extension. The MM_STATE macro must be explicitly declared exactly once by each JSExtension.

 18: MM_STATE  19:  20: void  21: MM_Init(void)  22: {  23:   JS_DefineFunction("addTwoNumbers", addTwoNumbers, 2);  24: } 

This macro calls the extension's MM_Init() function (lines 2024), which is where the extension declares the functions that will be exposed to the JavaScript environment and associates them with the internal C functions that will perform the necessary work with JS_DefineFunction() . In this example, the function name JS_DefineFunction() 's first argumentas seen by the JavaScript developer has the same name as the internal C function, but that is not required. We could have named the C function anything we wanted to, as long as it was a valid C function name.

The second argument is the C function within the extension that actually implements the logic for the function that the JavaScript programmer will call. The last argument is the number of arguments that the JavaScript function expects to receive.

Exposing Functions to the JavaScript Environment

In the final code block, lines 2644 declare the C implementation of the function that the JavaScript code will call.

 26: JSBool addTwoNumbers (JSContext* cx, JSObject* obj, unsigned  int argc, jsval* jsArgv, jsval* rval)  27: {  28:   int firstNumber;  29:   int secondNumber;  30:   int result;  31:  32:   // Get the arguments from JavaScript and convert them to C  33:   // data types.  34:   JS_ValueToInteger(cx, jsArgv[0], &firstNumber);  35:   JS_ValueToInteger(cx, jsArgv[1], &secondNumber);  36:  37:   // Add them together  38:   result = firstNumber + secondNumber;  39:  40:   // Convert the result to a JavaScript value and return it  41:   *rval = JS_IntegerToValue(result);  42:  43:   return JS_TRUE;  44: } 

Every C implementation function of a JavaScript method has the same function header and accepts the same arguments. Its basic format is this:

 JSBool function(JSContext *cx, JSObject *obj, unsigned int argc,  jsval *argv, jsval *rval) 

The first argument is a pointer to a JSContext . This is an opaque data type that is passed to all JSExtension methods that are called from the JavaScript environment, and you will need to pass it along to all of the functions that Dreamweaver exposes to your extension to operate on JavaScript objects. The second argument, a pointer to a JSObject , is the object within whose scope the extension function will execute. In the JavaScript namespace, this object corresponds to the object referenced by the "this" keyword. The third argument is the number of arguments that your function was called with in the JavaScript environment in Dreamweaver. The fourth argument is an array of jsval values, which represent the actual arguments that your extension function was called with. The last argument, rval , is a pointer to a jsval that your extension sets as the return value of the function in the JavaScript environment. For example, our extension will be called from the JavaScript environment with this code:

 var result = 0;  result = SampleExt.addTwoNumbers(5,6); 

The result that the JavaScript developer gets back from the function call is passed back from the C layer in the rval parameter.

All of your JSExtension methods that are called from the JavaScript environment must return a Boolean value of either JS_TRUE or JS_FALSE . This return value indicates to Dreamweaver whether your extension function executed without errors. If everything executes normally, then you should return JS_TRUE . A return value of JS_FALSE results in an error message being shown to the user. Unless you supply your own error message, a default message alert box will be displayed to the user. (The section entitled "Handling and Reporting Errors" provides details on supplying your own error messages.)

Converting JavaScript Data Types to C

When Dreamweaver passes arguments to your C extension, they are passed in JavaScript format. Before you can operate on these arguments, you must first convert them from JavaScript format into C data types. Similarly, when you're passing data back to the JavaScript environment from your extension, you must convert the data to JavaScript format before JavaScript-based extensions can use it.

The arguments that are passed to your extension from Dreamweaver are in a special JavaScript format represented by a data type called a "jsval" . JavaScript is a "loosely typed" language; in other words, JavaScript does not enforce restrictions on what types of data can be stored in a variable. For example, a JavaScript variable can contain a string, a number, an Object, and so on, and all of these types are stored in a jsval . The C language, however, is "strongly typed"; it expects programmers to explicitly declare what type of data a variable will contain before the program can use it. It is for this reason that you must convert all JavaScript arguments to C data types before attempting to operate on them.

Dreamweaver provides a set of functions that JSExtensions can use to convert JavaScript variables to C types. The following table lists the conversion functions and their purpose.

Function

Purpose

JS_ValueToString()

Convert a JavaScript value to a C-style string. Returns a character pointer (char *).

JS_ValueToInteger()

Convert a JavaScript value to a long integer.

JS_ValueToDouble()

Convert a JavaScript value to an 8-byte floating-point number.

JS_ValueToBoolean()

Convert a JavaScript value to a Boolean value.

JS_ValueToObject()

Convert a JavaScript value to a JSObject pointer.

All of these methods, with the exception of JS_ValueToString() , return a JSBool indicating whether the supplied jsval could be successfully converted to the requested data type. In the case of JS_ValueToString() , a return value of null indicates failure.

In lines 34 and 35 of the example, the extension method addTwoNumbers() calls JS_ValueToInteger() on the two arguments passed to the extension to convert them to C-style integers to add them together:

 34:   JS_ValueToInteger(cx, jsArgv[0], &firstNumber);  35:   JS_ValueToInteger(cx, jsArgv[1], &secondNumber); 

Converting C Data Types to JavaScript

Just as you must first convert JavaScript variables to C data types before your extension can operate on them, you must convert all C data types to JavaScript values before sending them back to the JavaScript environment. Each function that Dreamweaver provides for converting JavaScript values to C data types has a counterpart that performs the opposite function: converting C data types to JavaScript values. The following table lists those functions and their purpose.

Function

Purpose

JS_StringToValue()

Convert a C-style string to a JavaScript value.

JS_IntegerToValue()

Convert a long integer to a JavaScript value.

JS_DoubleToValue()

Convert an 8-byte floating point number to a JavaScript value.

JS_BooleanToValue()

Convert a Boolean value to a JavaScript value.

JS_ObjectToValue()

Convert a JSObject pointer to a JavaScript value.

In our example extension, line 41 converts the C-style integer result to a JavaScript value and stores the result in rval , which will pass the result back to the JavaScript caller:

 41:   *rval = JS_IntegerToValue(result); 

Handling Complex Data Types

The preceding methods are intended for dealing with basic data types, such as integers and strings. Dealing with more complex data types, such as arrays, is more complicated and requires a few additional utility functions.

Accessing data within arrays that have been passed to your extension as arguments requires converting the jsval that represents the Array to a JSObject , checking its type to make sure that it is an Array object, and retrieving each element from the array. Passing an array back to the JavaScript environment requires creating a new object to represent the array and adding each element to the array object.

The methods used to accomplish these tasks are described in the following table.

Function

Purpose

JS_ObjectType()

Return a description of a given JSObject.

JS_GetArrayLength()

Return the number of elements in an array.

JS_GetElement()

Retrieve an element from an array.

JS_SetElement()

Convert a Boolean value to a JavaScript value.

JS_NewArrayObject()

Create a new array object.

Retrieving Data from an Array

The steps required to retrieve elements from an array are relatively straightforward. Typically, they follow this general order:

  1. Convert the jsval that represents the array to a JSObject .

  2. Ensure that the resulting JSObject is an array by checking its type.

  3. Get the length of the array to determine how many elements are in it.

  4. Iterate over the array using a loop, retrieving one element at a time.

  5. Convert the element from a jsval to a C data type for processing.

The following code snippet shows how to retrieve data from an array:

 void workWithArray(JSContext*cx, jsval arrayVal)  {     JSObject *arrObj;     // first convert the jsval to a JSObject  JS_ValueToObject(cx, arrayVal, &arrObj);  // now make sure that we have an Object of type "Array"  if (!strcmp(JS_ObjectType(arrObj),"Array"))  {     // once we're sure we have an array, we can extract data     // get the length of the array     int length = JS_GetArrayLength(cx, arrObj);     for (int k=0; k < length; k++)     {        // get the next element from the array        jsval theVal;        JS_GetElement(cx, arrObj, k, &theVal);        // Do whatever you want with it...     }  }  } 
Adding Data to an Array

Adding data elements to an array for return to the JavaScript environment is only marginally more difficult than retrieving elements from an array. The general steps for adding elements to an array are as follows :

  1. Create a new array object.

  2. Convert each element you want to add to the array from a C data type to a jsval .

  3. Add the element to the array.

  4. Convert the array object to a jsval from a JSObject.

The following code snippet demonstrates how to add elements to a newly created array and return the array to the calling function as a jsval :

 jsval createNewArrayOfIntegers()  {     JSObject arrObj;     arrObj = JS_NewArrayObject(cx, 10, NULL);     for (int k=0; k<10; k++)     {        // add each new element        jsval theVal;        theVal = JS_IntegerToValue(i);        JS_SetElement(cx, arrObj, i, &theVal);     }     // convert the array to a jsval and return it     return JS_ObjectToValue(arrObj);  } 

TIP

There are two ways to call JS_NewArrayObject . You can either pass an array of jsval values as the third parameter to populate the array, or you can pass NULL as the third argument, and manually place the elements in the array as we did in the example. The first method works well if you already have an array of jsvals built; the second is better if you create each new jsval on the fly. Note that the length of the array must be specified when it is created.


Executing JavaScript Code from within C

Sometimes it can be useful to execute JavaScript code from within your C extensions. This is useful for several reasons; perhaps you have some JavaScript code that you want to hide from the public, or you want to take advantage of some built-in JavaScript functionality rather than rewrite it yourself. A good example of this is using regular expressions. JavaScript already has a good RegExp evaluation engine, so it's not necessary to try to build your own. The only problem is that your C code can't directly take advantage of this functionality; it is, after all, part of the JavaScript engine. Fortunately, Dreamweaver provides a way to call JavaScript code from within your C code just as if it were executing as a regular JavaScript extension.

The method you use to run JavaScript code from within your C extension is called JS_ExecuteScript , which has this prototype:

 JS_ExecuteScript(JSContext *cx, JSObject *obj, char *script,  unsigned int sz, jsval *rval); 

The JSContext argument is the same one that is passed to your extension. The JSObject argument represents the object whose scope the JavaScript code will execute in. This object is equal to the value of the "this" keyword while the script is running. It is usually the same as the one passed to your extension, but as we'll see shortly it can be an object of your own creation. The script argument is a string representing the actual JavaScript code that you want to execute, and the sz argument is the length of the script string. The last argument, rval , is a pointer to a jsval . If the JavaScript code you are executing returns a value, it will be returned to your C code in this argument.

To use a practical example, take the scenario outlined earlier: You want to use a regular expression from within your C code, but you don't have a RegExp library that you can use. One possible solution would be to write your own RegExp implementation, but that would consume precious development time and would become a maintenance headache . It would also detract from the focus of your extension and make your code larger than it has to be. A far better solution would be to use the RegExp engine that is already built into JavaScript.

The following code snippet demonstrates how to do this:

graphics/10icon02.gif

Here, we are performing a simple string search-and-replace pattern using a regular expression. First, we build a string of JavaScript code to execute in Dreamweaver and store it in the variable pTheJS . In this example, the string is hard coded, but we could have built up a regular C string from any arguments that we were given as part of the function. The part of the JavaScript code string that reads theStr.replace(/name/i,\"moniker\") is a regular expression that basically means "Replace the first instance of 'name' with 'moniker', and don't worry about whether the word 'name' is uppercase or lowercase." The result of the call to the RegExp replace() function is a string that contains the results of the pattern substitution.

The call to JS_ExecuteScript() will run this JavaScript snippet inside Dreamweaver and return a result in the retVal argument. Because this argument is a jsval just like any other, we can convert it from a jsval to a string using JS_ValueToString() , and then continue processing it just as any other C string.

Working with Complex Objects

Another good example of executing JavaScript code in C is demonstrated by assigning arbitrary properties to generic JavaScript objects. For example, consider the following JavaScript code snippet:

 function createNameObject()  {     var theObject = new Object();     theObject.firstName = "Joe";     theObject.lastName = "Marini";     return theObject;  } 

This function creates a JavaScript object and assigns two properties to it: firstName and lastName . Unfortunately, you can't do this using the provided Dreamweaver C functions; you can only create arrays and insert elements by index.

To accomplish the same thing in C as shown in the preceding JavaScript code, you could write the following function:

graphics/10icon03.gif

The first three lines are the JavaScript code that will create and set the properties of our new JavaScript object. They are hard-coded here for convenience of the example, but they could be built up from parameters just like any other C string.

The first call to JS_ExecuteScript() runs the code that actually creates a new object in the JavaScript environment and returns the created object to our C code.

Here's where we get tricky. We convert the return value to a JSObject , and then we use that JSObject as the subsequent JSObject parameter to the remaining JS_ExecuteScript() calls. This switches the scope of the current object to the one we just created, which means that all references to the "this" keyword in the JavaScript environment will operate on our new object. The next two calls to JS_ExecuteScript() call the JavaScript code that sets the properties of our newly created object using the "this" keyword.

Now all we need to do is convert the JSObject that we've been using back into a jsval , and we can return it to the JavaScript environment. There, the JavaScript developer can operate on it just like any other custom object. This is what the final call to JS_ObjectToValue() does.

Handling and Reporting Errors

At some point, it will be necessary to display one or more error messages to users of your extension. You could just return error codes from your extension and hope that developers will check for errors, but as we all know from experience, that rarely happens. Even if developers do in fact check for errors that come back from your extension, the chances that they will all coordinate among each other and display the same consistent error messages to the end user are slim to none.

To avoid this problem, Dreamweaver provides a way for C extensions to report execution errors that occur inside the C code to the end user in the same way that internal JavaScript engine errors are reported . This is accomplished by using the JS_ReportError() function, which has the following prototype:

 JS_ReportError(JSContext *cx, char *error, size_t sz); 

The first argument is the ubiquitous JSContext that is passed to all JSExtension methods that the JavaScript developer can call from JavaScript. The second argument is the error message to display. The third argument is the length of the string passed in the second argument. If you pass for this argument, then the length of the null- terminated string in argument two is computed for you.

You don't have to worry about reporting what line number the error occurred on. Dreamweaver takes care of this for you automatically.

How would you use this function? Let's take a fresh look at our original example code listing for addTwoNumbers() , the function that takes two numbers and adds them together:

 JSBool addTwoNumbers (JSContext* cx, JSObject* obj, unsigned int  argc, jsval* jsArgv, jsval* rval)  {  int firstNumber;  int secondNumber;  int result;  // Get the arguments from JavaScript and convert them to C  // data types.  firstNumber = JS_ValueToInteger(cx, jsArgv[0]);  secondNumber = JS_ValueToInteger(cx, jsArgv[1]);  // Add them together  result = firstNumber + secondNumber;     // Convert the result to a JavaScript value and return it     *rval = JS_IntegerToValue(result);     return JS_TRUE;  } 

This code is making a couple of rather startling assumptions, starting with the fact that it assumes two numbers are actually being passed to the function. Because JavaScript is a loosely typed language, the interpreter doesn't check to make sure that the function is actually receiving the same number of arguments that it expects. In addition, the two arguments passed might not even be numbers; they could be strings or objects.

To make this code more useful to JavaScript developers, we need to return some customized error messages instead of just the generic "A JavaScript error occurred" that you get for free with Dreamweaver. We need to let developers know specifically what went wrong and what to do to fix it. Consider the revised listing that follows:

 JSBool addTwoNumbers (JSContext* cx, JSObject* obj, unsigned int  argc, jsval* jsArgv, jsval* rval)  {  int firstNumber;  int secondNumber;  int result;     // Check to see if two arguments were actually passed!     if (argc < 2)     {        JS_ReportError(cx, "Wrong number of arguments passed to        addTwoNumbers!", 0);        return JS_FALSE; // return false in case of error     }  // Get the arguments from JavaScript and convert them to C  // data types.  if (!JS_ValueToInteger(cx, jsArgv[0], &firstNumber))  {        JS_ReportError(cx, "First argument isn't a number!", 0);        return JS_FALSE; // return false in case of error  }  if (!JS_ValueToInteger(cx, jsArgv[1], &secondNumber))  {        JS_ReportError(cx, "Second argument isn't a number!", 0);        return JS_FALSE; // return false in case of error  }  // Add them together  result = firstNumber + secondNumber;     // Convert the result to a JavaScript value and return it     *rval = JS_IntegerToValue(result);     return JS_TRUE;  } 

In this improved version of the function, we alert the JavaScript developer to two different types of problems. The first is that the function doesn't have enough arguments to work correctly. This error is handled by the code that checks the value of argc to see how many arguments were passed in. If fewer than two arguments were supplied, the function reports the error and returns. (Note: The function doesn't check for exactly two arguments because in the case that more than two were supplied, the function just uses the first two.)

The second type of error is handled by the code that checks the return values of the calls to JS_ValueToInteger , which returns false when the given argument cannot be converted to a number (that is, the user passed "abc").

TIP

When creating your own error messages, it's a good idea to store them in string resources so they are easily localizable.


Now, our function proceeds to add two numbers only if it was in fact given at least two valid numbers to add together, and reports accurate and descriptive errors if not.



Joseph Lowery's Beyond Dreamweaver
Joseph Lowerys Beyond Dreamweaver
ISBN: B000H2MWYS
EAN: N/A
Year: 2001
Pages: 87
Authors: Joseph Lowery

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net