Figure 1   Computer Finder's System Architecture

Figure 1 Computer Finder's System Architecture


Figure 4   Computer Document Type Definition Architecture

Figure 4 Computer Document Type Definition Architecture


Figure 5   The Computer DTD


 <!ELEMENT computers (computer*)>
 <!ELEMENT computer (vendor?, description?, disk?,
                     monitor?, cpu, ram, operatingsystem,
                     inputoutput*, removabledisk?)>
 <!ELEMENT description (#PCDATA)>
 <!ELEMENT disk EMPTY>
 <!ELEMENT monitor EMPTY>
 <!ELEMENT cpu EMPTY>
 <!ELEMENT ram EMPTY>
 <!ELEMENT operatingsystem EMPTY>
 <!ELEMENT inputoutput (mouse?, joystick?, speakers?)>
 <!ELEMENT mouse EMPTY>
 <!ELEMENT joystick EMPTY>
 <!ELEMENT speakers EMPTY>
 <!ELEMENT removabledisk EMPTY>
 <!ELEMENT vendor EMPTY>

 <!ATTLIST computer model CDATA #REQUIRED
                    price CDATA #REQUIRED>
 <!ATTLIST disk size CDATA #REQUIRED>
 <!ATTLIST monitor size (15 | 17 | 19 | 21) "15">
 <!ATTLIST cpu brand CDATA #REQUIRED
               speed CDATA #REQUIRED>
 <!ATTLIST ram size CDATA #REQUIRED>
 <!ATTLIST operatingsystem name (Win98 | WinNTW | WinNTS | Linux) "Win98">
 <!ATTLIST mouse type (mouse | trackball | touchpad) "mouse">
 <!ATTLIST removabledisk type (CDROM | CDRW | DVD) "CDROM">
 <!ATTLIST vendor name CDATA #REQUIRED
                  website CDATA #IMPLIED>

Figure 6   User Services HTML Page


 <html>

 <head>
 <title>Computer Finder</title>
 <meta name="GENERATOR" content="Microsoft FrontPage 3.0">
 <style type="text/css">
   .LabelStyle   {font-size:18; font-face:Arial; color:black; font-weight:bold;}
   .UnitsStyle   {font-size:14; font-face:Arial; color:black; font-weight:bold;}
   .ButtonStyle  {font-size:18; font-face:Arial; color:black; font-weight:bold;
                  height:40; width:150;}
   .InfoStyle    {font-size:18; font-face:Arial; color:green; font-weight:bold;}
 </style>
 </head>

 <body background="glass.gif">
 <!--
     User Services part of three-tier model.
     Form for user to select computer options.
     The server will use these specifications to query all of its
     known vendor sites.
 -->

 <p><img src="finder.gif" width="393" height="228" alt="finder.gif (5652 bytes)"><br>
 </p>
 <span class="InfoStyle">

 <p>Enter your desired features and price,<br>
 and we'll find the best deal on the Internet!</p>
 </span>

 <form name="ComputerOptionsForm" action="compbusi.asp">
   <table>
     <tr>
       <td><table border="0">
         <tr>
           <td class="LabelStyle">Price</td>
           <td><input type="text" name="Price" size="10" value="2000">
           <span class="UnitsStyle">
           (maximum)</span></td>
         </tr>
         <tr>
           <td class="LabelStyle">Disk</td>
           <td><input type="text" name="DiskSize" size="10" value="4">
           <span class="UnitsStyle"> GBs
           (minimum)</span></td>
         </tr>
         <tr>
           <td class="LabelStyle">Monitor</td>
           <td><input type="text" name="MonitorSize" size="10" value="15">
           <span class="UnitsStyle">
           inches (minimum)</span></td>
         </tr>
         <tr>
           <td class="LabelStyle">CPU Speed</td>
           <td><input type="text" name="CPUSpeed" size="10" value="300">
           <span class="UnitsStyle"> MHz
           (minimum)</span></td>
         </tr>
         <tr>
           <td class="LabelStyle">RAM</td>
           <td><input type="text" name="RAMAmount" size="10" value="32">
           <span class="UnitsStyle"> MBs
           (minimum)</span></td>
         </tr>
         <tr>
           <td class="LabelStyle">Operating<br>
           System</td>
           <td class="UnitsStyle"><select name="OperatingSystemType" size="1">
             <option selected>Windows 98</option>
             <option>Windows NT Workstation</option>
             <option>Windows NT Server</option></select></td>
         </tr>
         <tr>
           <td class="LabelStyle">Mouse</td>
           <td class="UnitsStyle"><select name="MouseType" size="1">
             <option selected>Standard</option>
             <option>Trackball</option>
             <option>Touchpad</option>
           </select></td>
         </tr>
         <tr>
           <td class="LabelStyle">CD Type</td>
           <td class="UnitsStyle"><select name="RemovableDiskType" size="1">
             <option selected>CDROM</option>
             <option>CD Read/Write</option>
             <option>DVD</option>
           </select></td>
         </tr>
         <tr>
           <td class="LabelStyle">Other Options</td>
           <td class="UnitsStyle"><input type="checkbox" name="JoystickOption"
           value="ON"> Joystick<input type="checkbox" name="SpeakersOption"
           value="ON">Speakers</td>
         </tr>
         <tr>
           <td class="LabelStyle">Display</td>
           <td class="UnitsStyle">
           <input type="radio" name="DisplayType" value="HTML" checked>HTML
           <input type="radio" name="DisplayType" value="TEXT">Text
           <input type="radio" name="DisplayType" value="CSS">XML/CSS
           <input type="radio" name="DisplayType" value="XSL">XML/XSL
           <input type="radio" name="DisplayType" value="XSL-S">
           XML/XSL (Server) </td>
         </tr>
         <tr>
           <td colspan="2"><input type="submit" name="SubmitButton" value="Submit"
           class="ButtonStyle"> </td>
         </tr>
       </table>
       </td>
     </tr>
   </table>
 </form></body></html>

Figure 7   Data Services ASP Page


 <%@ language="JScript" %>
 <%
 // Data Services part of three-tier model.

 // Company Web site for hyperlink in final user display.
 var WEB_SITE = "http://www.xyzcomputers.com";

 var strSQL; // Global string to hold SQL text.

 function BuildSQL()
 // Build the SQL query string, based on the parameters in the URL.
 {
   var objString;

   strSQL = "SELECT Computers.Model AS Model, " +
            "       Computers.Price AS Price, " +
            "       Computers.Description AS Description, " +
            "       Computers.DiskSize AS DiskSize, " +
            "       Computers.MonitorSize AS MonitorSize, " +
            "       CPUs.Speed AS CPUSpeed, " +
            "       CPUs.Manufacturer AS CPUManufacturer, " +
            "       Computers.RAMSize AS RAMSize, " +
            "       OperatingSystems.Id AS OperatingSystemId, " +
            "       PointingDevices.Id AS PointingDeviceId, " +
            "       Computers.Joystick AS IncludeJoystick, " +
            "       Computers.Speakers AS IncludeSpeakers, " +
            "       RemovableDisks.Id AS RemovableDiskId" +
            " FROM  Computers, " +
            "       CPUs, " +
            "       OperatingSystems, " +
            "       PointingDevices, " +
            "       RemovableDisks " +
            " WHERE Computers.CPUId = CPUs.Id " +
            "   AND Computers.OperatingSystemId = OperatingSystems.Id " +
            "   AND Computers.PointingDeviceId = PointingDevices.Id " +
            "   AND Computers.RemovableDiskId = RemovableDisks.Id ";

   // Should verify the argument types passed in, such as for numerics.
   AddToWhere(Request.QueryString("Price").Item, "Computers.Price <=");
   AddToWhere(Request.QueryString("DiskSize").Item, "Computers.DiskSize >=");
   AddToWhere(Request.QueryString("MonitorSize").Item,
              "Computers.MonitorSize >=");
   AddToWhere(Request.QueryString("CPUSpeed").Item, "CPUs.Speed >=");
   AddToWhere(Request.QueryString("RAMSize").Item, "Computers.RAMSize >=");
   if (IsAString(Request.QueryString("OperatingSystem").Item))
   {
     objString = new String(Request.QueryString("OperatingSystem").Item);
     objString = objString.toUpperCase();
     if (objString.indexOf("98") >= 0)  { // Assume Windows 98
       AddToWhere("1", "OperatingSystems.Id =");
     }
     else if ((objString.indexOf("WORKSTATION") >= 0) ||
              (objString.indexOf("NTW") >= 0)) {
       // Assume "Windows NT Workstation".
       AddToWhere("2", "OperatingSystems.Id =");
     }
     else if ((objString.indexOf("SERVER") >= 0) ||
              (objString.indexOf("NTS") >= 0)) {
       // Assume "Windows NT Server".
       AddToWhere("3", "OperatingSystems.Id =");
     }
     else if (objString.indexOf("LINUX") != null) {
       AddToWhere("4", "OperatingSystems.Id =");
     }
     delete objString;
   }
   if (IsAString(Request.QueryString("Mouse").Item))
   {
     objString = new String(Request.QueryString("Mouse").Item);
     objString = objString.toUpperCase();
     if (objString.indexOf("MOUSE") >= 0) {
       AddToWhere("1", "PointingDevices.Id =");
     }
     else if (objString.indexOf("TRACKBALL") >= 0) {
       AddToWhere("2", "PointingDevices.Id =");
     }
     else if (objString.indexOf("TOUCHPAD") >= 0) {
       AddToWhere("3", "PointingDevices.Id =");
     }
     delete objString;
   }

   if (IsAString(Request.QueryString("RemovableDisk").Item)) {
     objString = Request.QueryString("RemovableDisk").Item;
     objString = objString.toUpperCase();
     if ((objString.indexOf("CDROM") >= 0) ||
         (objString.indexOf("CD-ROM") >= 0)) {
       // Could be CD-ROM, CDROM.
       AddToWhere("1", "RemovableDisks.Id =");
     }
     else if ((objString.indexOf("CD") >= 0) &&
         ((objString.indexOf("WRITE") >= 0)
           || (objString.indexOf("RW") >= 0))) {
       // Could be CD Write, CD ReadWrite, CDRW.
       AddToWhere("2", "RemovableDisks.Id =");
     }
     else if (objString.indexOf("DVD") >= 0) {
       AddToWhere("3", "RemovableDisks.Id =");
     }
     delete objString;
   }
   if (IsAString(Request.QueryString("Joystick").Item)) {
     AddToWhere("1", "Computers.Joystick =");
   }
   if (IsAString(Request.QueryString("Speakers").Item)) {
     AddToWhere("1", "Computers.Speakers =");
   }
 }

 function AddToWhere(strArg, strComp)
 // Add a constraint to the SQL 'Where' clause.
 {
   if (IsAString(strArg)) {
     strSQL = strSQL + " AND " + strComp + " " + strArg + " ";
   }
 }

 function IsAString(something)
 // Is the argument a string?
 {
   if (typeof(something) == "string") {
     return (true);
   }
   else {
     return (false);
   }
 }

 function BuildXML()
 // Build the XML, based on the query results. This will go back to the business
 // services page, which will combine it with results from other vendors.
 {
   var objConn;
   var objResult;

   Response.Write("<?xml version=\"1.0\"?>\n");
   Response.Write("<!DOCTYPE computers SYSTEM \"computer.dtd\">\n")
   Response.Write("<computers>\n");

   objConn = Server.CreateObject("ADODB.Connection");
   objConn.Open("XYZComputers");
   objResult = objConn.Execute(strSQL);
   if (!objResult.EOF)
   {
     objResult.MoveFirst();
     while (!objResult.EOF)
     {
       Response.Write("  <computer model=\"" + objResult("Model").Value
                      + "\" price=\"" + objResult("Price").Value + "\">\n");
       // Add vendor information, which is not from query.
       Response.Write("    <vendor name=\"XYZ Computers\" " +
                      "website=\"" + WEB_SITE + "\"/>\n");
       Response.Write("    <description>\n      "
                      + objResult("Description").Value +
                      "\n    </description>\n");
       Response.Write("    <disk size=\""
                      + objResult("DiskSize").Value + "\"/>\n");
       Response.Write("    <monitor size=\""
                      + objResult("MonitorSize").Value + "\"/>\n");
       Response.Write("    <cpu brand=\""
                      + objResult("CPUManufacturer").Value +
                      "\" speed=\"" + objResult("CPUSpeed").Value + "\"/>\n");
       Response.Write("    <ram size=\""
                      + objResult("RAMSize").Value + "\"/>\n");
       if (objResult("OperatingSystemId").Value == "2")
       {
         Response.Write("    <operatingsystem name=\"WinNTW\"/>\n");
       }
       else if (objResult("OperatingSystemId").Value == "3")
       {
         Response.Write("    <operatingsystem name=\"WinNTS\"/>\n");
       }
       else if (objResult("OperatingSystemId").Value == "4")
       {
         Response.Write("    <operatingsystem name=\"Linux\"/>\n");
       }
       else { // default
       Response.Write("    <operatingsystem name=\"Win98\"/>\n");
       }
       Response.Write("    <inputoutput>\n");
       if (objResult("PointingDeviceId").Value == "2"){
         Response.Write("      <mouse type=\"trackball\"/>\n");
       }
       else if (objResult("PointingDeviceId").Value == "3"){
         Response.Write("      <mouse type=\"touchpad\"/>\n");
       }
       else { // default
         Response.Write("      <mouse type=\"mouse\"/>\n");
       }
       if (objResult("IncludeJoystick").Value){
         Response.Write("      <joystick/>\n");
       }
       if (objResult("IncludeSpeakers").Value){
         Response.Write("      <speakers/>\n");
       }
       Response.Write("    </inputoutput>\n");
       if (objResult("RemovableDiskId").Value == "2"){
         Response.Write("    <removabledisk type=\"CDRW\"/>\n");
       }
       else if (objResult("RemovableDiskId").Value == "3"){
         Response.Write("    <removabledisk type=\"DVD\"/>\n");
       }
       else { // default
         Response.Write("    <removabledisk type=\"CDROM\"/>\n");
       }
       Response.Write("  </computer>\n");
       objResult.MoveNext();
     }
   }
   objConn.Close();
   Response.Write("</computers>");
 }

 BuildSQL();  // Build the SQL statement from the URL arguments.
 BuildXML();  // Execute the SQL statement, and create the XML result.
              // XML is sent to the business services page that called us.

 %>

Figure 8   Business Services ASP Page


 <%@ language="JScript" %>
 <%

 // Business Services part of three-tier model.
 // Process user query by contacting all known vendors.
 // The code also illustrates multiple output options.

 // XML DOM node types.
 var ELEMENT_NODE                = 1;
 var ATTRIBUTE_NODE              = 2;
 var TEXT_NODE                   = 3;
 var CDATA_SECTION_NODE          = 4;
 var ENTITY_REFERENCE_NODE       = 5;
 var ENTITY_NODE                 = 6;
 var PROCESSING_INSTRUCTION_NODE = 7;
 var COMMENT_NODE                = 8;
 var DOCUMENT_NODE               = 9;
 var DOCUMENT_TYPE_NODE          = 10;
 var DOCUMENT_FRAGMENT_NODE      = 11;
 var NOTATION_NODE               = 12;

 var HTML_CSS_FILE               = "compuser.css";
 var XML_CSS_FILE                = "compuser.csx";
 var XML_XSL_FILE                = "compuser.xsl";

 // Vendor file contains URLs of vendors to check.
 var VENDOR_FILE = "e:\\inetpub\\wwwroot\\compxml\\vendurl.txt";

 var intElemLevel = 0; // Global to count XML element level
                       // for recursive function.
 var strReturnURL;     // Global for HTML query page.
 var intNumResults;    // Number of query results
 var strXML;           // Global XML string.
 var strHTML;          // Global HTML string.

 function AddXMLBody(objNode)
 {
   // Gets XML query results from all vendors, combining results.

   var intNumElem;       // Number of elements in the parsed XML tree.
   var intNumAttr;       // Number of attributes for specific element.
   var intElemIndex;     // Element loop index.
   var intAttrIndex;     // Attribute loop index.
   var intNodeIndex;     // Element node loop index.
   var objThisElem;      // Current element.
   var objThisAttr;      // Current attribute.
   var objThisNode;      // Current node.
   var strNodeText;      // Node text string.
   var blnAnySub;        // Boolean flag.

   intElemLevel++;
   intNumElem = objNode.childNodes.length;
   for (intElemIndex = 0; intElemIndex < intNumElem; intElemIndex++)
   // Loop through child nodes for this element.
   {
     objThisElem = objNode.childNodes(intElemIndex);
     if (objThisElem.nodeType != ELEMENT_NODE) {
       // We know we are only dealing with element nodes at this level.
       continue;
     }
     strXML = strXML + indent(intElemLevel) + "<" + objThisElem.nodeName;
     intNumAttr = objThisElem.attributes.length;
     for (intAttrIndex = 0; intAttrIndex < intNumAttr; intAttrIndex++) {
       // Loop through attributes for this element.
       objThisAttr = objThisElem.attributes(intAttrIndex);
       strXML = strXML + " " + objThisAttr.nodeName + "=\""
                + objThisAttr.nodeValue + "\"";
     }
     blnAnySub = false;
     strNodeText = "";
     for (intNodeIndex = 0; intNodeIndex < objThisElem.childNodes.length;
          intNodeIndex++) {
     // See if this element has any element child nodes.
     // Also, look for text nodes.
       objThisNode = objThisElem.childNodes(intNodeIndex);
       if (objThisNode.nodeType == TEXT_NODE) {
         strNodeText = indent(intElemLevel+1) + trimws(objThisNode.nodeValue)
                       + "\n";
         blnAnySub = true;
         break; // Assume at most one text node per element.
       }
       else if (objThisNode.nodeType == ELEMENT_NODE) {
         blnAnySub = true;
       }
     }
     if (!blnAnySub) {
       strXML = strXML + "/>\n";
       continue;   // This element is complete.
     }
     // Recurse through children of this element node.
     strXML = strXML + ">\n" + strNodeText;
     AddXMLBody(objThisElem);
     strXML = strXML + indent(intElemLevel) + "</"
              + objThisElem.nodeName + ">\n";
   }
   intElemLevel--;
 }

 function indent(intNum)
 // Indents XML, so it looks nicer.
 {
   var strString = "";
   for (var intIndex = 0; intIndex < intNum; intIndex++) {
     strString = strString + "  ";
   }
   return(strString);
 }

 function trimws(strString)
 // Trims white space from beginning and ending of a string.
 // Could have cheated and used VBScript's Trim() function.
 {
   var intBegin, intEnd;
   var strResult;
   var chrChar;
   var flg;

   intEnd = strString.length - 1;
   intBegin = 0;
   flg = true;
   chrChar = strString.charAt(intBegin);
   while ((intBegin < intEnd) && ((chrChar == "\n") || (chrChar == "\r") ||
                                  (chrChar == "\t") || (chrChar == " "))) {
     // Find first non-whitespace.
     chrChar = strString.charAt(++intBegin);
   }
   chrChar = strString.charAt(intEnd);
   while ((intEnd >= 0) && ((chrChar == "\n") || (chrChar == "\r") ||
                            (chrChar == "\t") || (chrChar == " "))) {
     // Find last non-whitespace.
     chrChar = strString.charAt(--intEnd);
   }
   if (intBegin < intEnd) {
     strResult = strString.substr(intBegin, intEnd - intBegin + 1);
   }
   else {
     strResult = "";
   }
   return(strResult);
 }

 function BuildURL(strThisURL)
 // Build the URL to send to the vendors, based on the user's query.
 {
   var strNewURL;
   var strItem;
   var strString;

   strNewURL = trimws(strThisURL);
   if (strNewURL.length = 0) {
     return(strNewURL);
   }
   strNewURL = strNewURL + "?"; // Adding URL arguments.
   if (IsAString(strItem = Request.QueryString("Price").Item)) {
     strNewURL = strNewURL + "Price=" + strItem + "&";
   }
   if (IsAString(strItem = Request.QueryString("DiskSize").Item)) {
     strNewURL = strNewURL + "DiskSize=" + strItem + "&";
   }

   if (IsAString(strItem = Request.QueryString("MonitorSize").Item))
   {
     strNewURL = strNewURL + "MonitorSize=" + strItem + "&";
   }
   if (IsAString(strItem = Request.QueryString("CPUSpeed").Item)) {
     strNewURL = strNewURL + "CPUSpeed=" + strItem + "&";
   }
   if (IsAString(strItem = Request.QueryString("RAMSize").Item)) {
     strNewURL = strNewURL + "RAMSize=" + strItem + "&";
   }
   // Turn Operating System query value into one suitable for vendor query.
   if (IsAString(strItem = Request.QueryString("OperatingSystem").Item)) {
     strString = new String(strItem);
     strString = strString.toUpperCase();
     if (strString.indexOf("98") >= 0)  {
     // Assume "Windows 98".
       strNewURL = strNewURL + "OperatingSystem=Windows98";
     }
     else if ((strString.indexOf("WORKSTATION") >= 0) ||
              (strString.indexOf("NTW") >= 0)) {
     // Assume "Windows NT Workstation".
       strNewURL = strNewURL + "OperatingSystem=WindowsNTWorkstation";
     }
     else if ((strString.indexOf("SERVER") >= 0) ||
              (strString.indexOf("NTS") >= 0)) {
     // Assume "Windows NT Server".
       strNewURL = strNewURL + "OperatingSystem=WindowsNTServer";
     }
     else if (strString.indexOf("LINUX") != null)  {
       strNewURL = strNewURL + "OperatingSystem=Linux";
     }
     delete strString;
   }
   if (IsAString(strItem = Request.QueryString("MouseType").Item)) {
     strNewURL = strNewURL + "Mouse=" + strItem + "&";
   }
   if (IsAString(strItem = Request.QueryString("RemovableDisk").Item)) {
     strNewURL = strNewURL + "RemovableDisk=" + strItem + "&";
   }
   if (IsAString(strItem = Request.QueryString("JoystickOption").Item)) {
     strNewURL = strNewURL + "Joystick=Yes&";
   }
   if (IsAString(strItem = Request.QueryString("SpeakersOption").Item)) {
     strNewURL = strNewURL + "Speakers=Yes&";
   }

   if ((strNewURL.charAt(strNewURL.length-1) == "?") ||
       (strNewURL.charAt(strNewURL.length-1) == "&")) {
     strNewURL = strNewURL.substr(0,strNewURL.length-1);
   }
   return(strNewURL);
 }

 function IsAString(something)
 // Check if the data item is a string.
 {
   if (typeof(something) == "string") {
     return (true);
   }
   else {
     return (false);
   }
 }

 function AddHTMLBody(objNode)
 // Build HTML query results from all vendors, combining results.
 {
   var strVendorName;
   var strWebSite;
   var strModel;
   var strDescription;
   var strPrice;
   var strDiskSize;
   var strMonitorSize;
   var strCPU;
   var strRAM;
   var strOperatingSystem;
   var strMouse;
   var strSpeakers;
   var strJoystick;
   var strRemovableDisk;
   var intComputers;
   var objThisComputer;
   var intElemIndex;
   var intSubElemIndex;
   var intNumElem;
   var objThisElem;
   var intIOs;
   var objThisIO;
   var intIOIndex;
   var strThisVendor;

   // Start an HTML table.
   strHTML = strHTML + "<TABLE CLASS=\"DataTable\">\n";
   strThisVendor = "";
   intComputers = objNode.childNodes.length;
   for (intElemIndex = 0; intElemIndex < intComputers; intElemIndex++)
   // Loop through second level Computer elements.
   {
     // Initialize query values.
     strVendorName         = "";
     strWebSite            = "";
     strModel              = "";
     strDescription        = "";
     strPrice              = "";
     strDiskSize           = "";
     strMonitorSize        = "";
     strCPU                = "";
     strRAM                = "";
     strOperatingSystem    = "";
     strMouse              = "";
     strSpeakers           = "";
     strJoystick           = "";
     strRemovableDisk      = "";

     objThisComputer = objNode.childNodes(intElemIndex);
     if (objThisComputer.nodeType != ELEMENT_NODE) {
       continue;
     }
     strModel = GetAttributeValue(objThisComputer,"model");
     strPrice = "$" + GetAttributeValue(objThisComputer,"price");
     intNumElem = objThisComputer.childNodes.length;
     for (intSubElemIndex = 0; intSubElemIndex < intNumElem;
          intSubElemIndex++) {
     // Assume all child elements of Computer element are one level down.
       objThisElem = objThisComputer.childNodes(intSubElemIndex);
       switch (objThisElem.nodeName) {
       case "vendor":
         strVendorName = GetAttributeValue(objThisElem,"name");
         strWebSite = GetAttributeValue(objThisElem,"website");
         break;
       case "description":
         if (objThisElem.firstChild.nodeType == TEXT_NODE) {
           // Assume text node is first child.
           strDescription = objThisElem.firstChild.nodeValue;
         }
         break;
       case "disk":
         strDiskSize = GetAttributeValue(objThisElem,"size") + " GB Disk";
         break;
       case "monitor":
         strMonitorSize = GetAttributeValue(objThisElem,"size")
                          + " in Monitor";
         break;
       case "cpu":
         strCPU = GetAttributeValue(objThisElem,"brand") + ": " +
                  GetAttributeValue(objThisElem,"speed") + " Mhz";
         break;
       case "ram":
         strRAM = GetAttributeValue(objThisElem,"size") + " MB RAM";
         break;
       case "operatingsystem":
         strOperatingSystem = GetAttributeValue(objThisElem,"name");
         break;
       case "inputoutput":
         intIOs = objThisElem.childNodes.length;
         for (intIOIndex = 0; intIOIndex < intIOs; intIOIndex++) {
         // Loop through child nodes of InputOutput node.
           objThisIO = objThisElem.childNodes(intIOIndex);
           switch (objThisIO.nodeName) {
               case "mouse":
                   strMouse = "Mouse";
                   break;
           case "speakers":
             strSpeakers = "Speakers";
             break;
           case "joystick":
             strJoystick = "Joystick";
             break;
           }
         }
         break;
       case "removabledisk":
         strRemovableDisk = GetAttributeValue(objThisElem,"type");
         break;
       }
     }

     if (strVendorName != strThisVendor)
     // Add a Vendor row if this Computer is from a different vendor.
     {
       if (strThisVendor.length > 0)
       {
         strHTML = strHTML + "  <TR><TD COLSPAN=\"10\"><HR></TD></TR>\n";
       }
       strHTML = strHTML + "  <TR>\n";
       strHTML = strHTML + "    <TD COLSPAN=\"10\" CLASS=\"VendorLine\">\n";
       strHTML = strHTML + strVendorName + ": <A HREF=\"" +
                           strWebSite + "\">" + strWebSite + "</A><BR>\n";
       strHTML = strHTML + "    </TD>\n";
       strHTML = strHTML + "  </TR>\n";
       strThisVendor = strVendorName;
     }
     // Add row of data.
     strHTML = strHTML + "  <TR>\n";
     strHTML = strHTML + "    <TD COLSPAN=\"10\" CLASS=\"ModelLine\">\n";
     strHTML = strHTML + strModel + ": " + strDescription + "\n";
     strHTML = strHTML + "    </TD>\n";
     strHTML = strHTML + "  </TR>\n";

     strHTML = strHTML + "  <TR>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strPrice + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strDiskSize + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strMonitorSize + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strCPU + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strRAM + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strOperatingSystem + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strRemovableDisk + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strMouse + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strSpeakers + "</TD>\n";
     strHTML = strHTML + "    <TD CLASS=\"DataLine\">"
                       + strJoystick + "</TD>\n";
     strHTML = strHTML + "  </TR>\n";
   }
   strHTML = strHTML + "  <TR><TD COLSPAN=\"10\"><HR></TD></TR>\n";
   strHTML = strHTML + "</TABLE>\n";
 }

 function GetAttributeValue(objNode,strAttr)
 // Get a named attribute for an element.
 {
   return objNode.attributes.getNamedItem(strAttr).nodeValue;
 }

 function GetParserError(objDocument)
 // Get any errors from XML parser.
 {
   var errstr;

   errstr = "<P CLASS=\"ErrorMessage\">" +
            "Invalid XML<BR>" +
            "URL: " + objDocument.parseError.url + "<BR>" +
            "Line: " + objDocument.parseError.line + "<BR>" +
            "Character: " + objDocument.parseError.linepos + "<BR>" +
            "Source Text: " + objDocument.parseError.srcText + "<BR>" +
            "Error Code: " + objDocument.parseError.errorCode + "<BR>" +
            "Description: " + objDocument.parseError.reason + "<BR>" +
            "</P>";
   return errstr;
 }

 function GetCurrPath()
 // Get the current path of this ASP file.
 {
   var strPath;
   var intPosition;

   strPath = Request.ServerVariables("PATH_TRANSLATED").Item;
   // Find the last backslash.
   for (intPosition = strPath.length - 1; intPosition >= 0; intPosition--)
   {
     if (strPath.charAt(intPosition) == "\\")
     {
       break;
     }
   }
   if (intPosition >= 0)
   {
     return strPath.substr(0, intPosition+1)
   }
   else
   {
     return "\\";
   }
 }

 function MassageXML(objDocument)
 // CSS can't add labels, create tables, create hyperlinks, etc.
 // Also, we have to assume the data is ordered correctly.
 // So, we will massage the XML DOM in preparation for CSS.
 // Actually, rather than go through this step, it's better to
 // just create the HTML or use XSL - but this provides a chance
 // to illustrate XML DOM manipulation.
 //
 // The massage process basically turns attributes into text nodes,
 // complete with any preceding or trailing labels. It also
 // adds an attribute to turn the vendor's Web site into a hyperlink.
 //
 // Like in AddHTMLBody, we are assuming that all Computer elements
 // are one level down, and all child elements of Computer are one
 // level below it.
 {
   var intComputers;
   var objThisComputer;
   var intElemIndex;
   var intSubElemIndex;
   var intNumElem;
   var objThisElem;
   var strIO;
   var intIOs;
   var objThisIO;
   var intIOIndex;
   var objNewNode;
   var objNode;

   objNode = objDocument.documentElement;
   intComputers = objNode.childNodes.length;
   for (intElemIndex = 0; intElemIndex < intComputers; intElemIndex++)
   // Loop through top-level Computer elements.
   {
     objThisComputer = objNode.childNodes(intElemIndex);
     if (objThisComputer.nodeType != ELEMENT_NODE)
     {
       continue;
     }
     intNumElem = objThisComputer.childNodes.length;
     for (intSubElemIndex = 0; intSubElemIndex < intNumElem;
          intSubElemIndex++)
     // Loop through child elements of Computer element.
     {
       objThisElem = objThisComputer.childNodes(intSubElemIndex);
       switch (objThisElem.nodeName) {
       // Create text nodes for elements, as we want their values to appear.
       case "vendor":
         // Create an attribute for the Web Site attribute to
         // create hyperlink text.
         objNewNode = objDocument.createTextNode("<BR/>" +
           GetAttributeValue(objThisElem,"name") + ": " +
           "<A HREF=\"" + GetAttributeValue(objThisElem,"website") +
           "\">" + GetAttributeValue(objThisElem,"website") + "</A>");
         objThisElem.appendChild(objNewNode);
         break;
       case "description":
         if (objThisElem.firstChild.nodeType == TEXT_NODE) {
           objThisElem.firstChild.nodeValue = "<BR/>" +
             GetAttributeValue(objThisComputer,"model") + ": " +
             objThisElem.firstChild.nodeValue + "<BR/>";
         }
         break;
       case "disk":
         objNewNode = objDocument.createTextNode(
           GetAttributeValue(objThisElem,"size") + "GB Disk");
         objThisElem.appendChild(objNewNode);
         break;
       case "monitor":
         objNewNode = objDocument.createTextNode(
           GetAttributeValue(objThisElem,"size") + "in Monitor");
         objThisElem.appendChild(objNewNode);
         break;
       case "cpu":
         objNewNode = objDocument.createTextNode(
           GetAttributeValue(objThisElem,"brand") + ":" +
           GetAttributeValue(objThisElem,"speed") + "Mhz");
         objThisElem.appendChild(objNewNode);
         break;
       case "ram":
         objNewNode = objDocument.createTextNode(
           GetAttributeValue(objThisElem,"size") + "MB RAM");
         objThisElem.appendChild(objNewNode);
         break;
       case "operatingsystem":
         objNewNode = objDocument.createTextNode(
           GetAttributeValue(objThisElem,"name"));
         objThisElem.appendChild(objNewNode);
         break;
       case "inputoutput":
         strIO = "";
         intIOs = objThisElem.childNodes.length;
         for (intIOIndex = 0; intIOIndex < intIOs; intIOIndex++) {
           objThisIO = objThisElem.childNodes(intIOIndex);
           switch (objThisIO.nodeName) {
           case "mouse":
             strIO = strIO + " Mouse";
             break;
           case "speakers":
             strIO = strIO + " Speakers";
             break;
           case "joystick":
             strIO = strIO + " Joystick";
             break;
           }
         }
         objNewNode = objDocument.createTextNode(strIO);
         objThisElem.appendChild(objNewNode);
         break;
       case "removabledisk":
         objNewNode = objDocument.createTextNode(
           GetAttributeValue(objThisElem,"type"));
         objThisElem.appendChild(objNewNode);
         break;
       }
     }
   }
 }

 function AddHTMLHeader(strCSSFile)
 // Add HTML header stuff for HTML output.
 {
   strHTML = "<HTML>\n<HEAD>\n";
   if (strCSSFile.length > 0) {
     // Add reference to CSS file.
     strHTML = strHTML
               + "<LINK REL=\"STYLESHEET\" TYPE=\"text/css\" HREF=\""
               + strCSSFile + "\">\n";
   }
   strHTML = strHTML + "</HEAD><BODY>\n";
   // Add a 'Return To' label.
   strHTML = strHTML + "<A HREF=\"" + strReturnURL
             + "\">Return to Computer Finder</A>\n" + "<HR>\n";
 }

 function AddHTMLFooter()
 // Add HTML footer stuff for HTML output.
 {
 //  if (blnAddXML) { // This option is mainly for debugging.
 //    strHTML = strHTML + "<plaintext>" + strXML + "</plaintext>";
 //  }
   strHTML = strHTML + "</BODY></HTML>\n";
 }

 function AddXMLHeader(strXSLFile)
 // Add XML header stuff for XML output.
 {
   var strType;

   strXML = "<?xml version= \"1.0\"?>\n";
   strType = "";

   // See if XSL or CSS file passed in.
   if (strXSLFile.search(/\.xsl/i) > 0) {
     strType = "text/xsl";
   }
   else if ((strXSLFile.search(/\.csx/i) > 0) ||
            (strXSLFile.search(/\.css/i) > 0)) {
   // CSS = CSS file for HTML, CSX = CSS file for XML.
     strType = "text/css";
   }
   if (strType.length > 0) {
     // Add processing instruction for XSL or CSS file.
     strXML = strXML + "<?xml-stylesheet " + "type=\"" + strType + "\" "
              + "href=\"" + strXSLFile + "\"?>\n";
   }

   // If strXML subsequently parsed, then the full DTD path must be
   // supplied, thus the need for GetCurrPath. If parse from URL,
   // this is not needed since parser looks in the same directory.
   // strXML= strXML + "<!DOCTYPE computers SYSTEM \""
   // + GetCurrPath() + "computer.dtd\">\n";
   strXML = strXML + "<computers>\n";
 }

 function AddXMLFooter()
 // Add XML footer stuff for XML output.
 {
   strXML = strXML + "</computers>\n";
 }

 function OutputParserError(objDocument)
 // Output an HTML page with XML parser error.
 {
   AddHTMLHeader(HTML_CSS_FILE);
   strHTML = strHTML + GetParserError(objDocument);
   AddHTMLFooter();
   Response.Write(strHTML);
 }

 function OutputNoResultsError()
 // Output an HTML page with no results error.
 {
   AddHTMLHeader(HTML_CSS_FILE);
   strHTML = strHTML +
       "<P CLASS=\"ErrorMessage\">No matches found for query.</P>";
   AddHTMLFooter();
   Response.Write(strHTML);
 }

 var objVendorFileSystem;
 var objVendorFile;
 var strURL;
 var objXMLDocument;
 var strXMLBody;
 var intDisplayType;
 var DISPLAY_XML         = 0;
 var DISPLAY_HTML        = 1;
 var DISPLAY_CSS         = 2;
 var DISPLAY_XSL         = 3;
 var DISPLAY_XSL_SERVER  = 4;
 var DISPLAY_TEXT        = 5;
 var PARSER_ERROR        = 100;
 var NO_RESULTS_ERROR    = 101;

 intDisplayType = DISPLAY_XML;

 // Set the 'Return To' hyperlink URL.
 strReturnURL = Request.ServerVariables("HTTP_REFERER");

 // See what kind of display was requested.
 // In real life, we would look at the browser capabilities
 // to determine this.
 switch (Request.QueryString("DisplayType").Item) {
 case "HTML":
 // Format for HTML output.
   intDisplayType = DISPLAY_HTML;
   break;
 case "CSS":
 // Format for XML/CSS output.
   intDisplayType = DISPLAY_CSS;
   break;
 case "XSL":
 // Format for XML/XSL output.
   intDisplayType = DISPLAY_XSL;
   break;
 case "XSL-S":
 // Format for XML/XSL output, but do transformation here, not at client.
   intDisplayType = DISPLAY_XSL_SERVER;
   break;
 case "TEXT":
 // Format as plain text HTML.
   intDisplayType = DISPLAY_TEXT;
   break;
 default:
 // Format for XML/XSL output.
   intDisplayType = DISPLAY_XML;
   break;
 }

 objXMLDocument = new ActiveXObject("microsoft.xmldom");

 // Open the file of vendor URLs.
 objVendorFileSystem = new ActiveXObject("Scripting.FileSystemObject");
 objVendorFile = objVendorFileSystem.OpenTextFile(VENDOR_FILE);

 // Construct XML string by:
 //   Loop through all the vendors:
 //     Execute the vendor URL to retrieve database information.
 //     Parse the retrieved XML.
 //     Add to the XML string.
 //
 // There are more efficient ways of doing this, but this process assures:
 //   Vendor XML is well formed.
 //   There are no 'surprises' in the resulting XML sent to the user.
 // Plus, it allows further manipulation of the XML from vendor to user,
 // if XSL is not being used. XSL could handle the transformations.

 strXML = "";
 while (objVendorFile.AtEndOfStream == 0)
 {
   strURL = objVendorFile.ReadLine(); // Get URL for next vendor.
   if (strURL.length > 0)
   {
     if (strURL.charAt(0) == "#") // comment
     {
       continue;
     }

     objXMLDocument.async = false;
     strURL = BuildURL(strURL);

     // Execute the vendor ASP page, then parse the result.
     // Note: load method loads a specified file. XML processor is smart
     //       enough to recognize the ASP extension, and process the page.
     objXMLDocument.load(strURL);
     if ((objXMLDocument.parseError.errorCode == 0))
     {
       AddXMLBody(objXMLDocument.documentElement); // Builds XML string.
     }
     else
     {
       intDisplayType = PARSER_ERROR;
       break;
     }
   }
 }
 strXMLBody = strXML; // Add headers and footers below.

 objVendorFile.Close();

 // Now see how many results we got.
 intNumResults = 0;
 AddXMLHeader("");
 strXML = strXML + strXMLBody;
 AddXMLFooter();
 objXMLDocument.loadXML(strXML); // Parse the XML.
 if ((objXMLDocument.parseError.errorCode == 0))
 {
   intNumResults = objXMLDocument.documentElement.childNodes.length;
 }
 if (intNumResults == 0)
 {
   intDisplayType = NO_RESULTS_ERROR;
 }

 switch (intDisplayType)
 {
 case PARSER_ERROR: // Parser error
   OutputParserError(objXMLDocument);
   break;

 case NO_RESULTS_ERROR: // No results for query.
   OutputNoResultsError();
   break;

 case DISPLAY_HTML: // Formatted HTML.
   // Construct HTML string by:
   //   Write the header.
   //   Process the parsed XML.
   //   Write the footer.

   AddXMLHeader("");
   strXML = strXML + strXMLBody;
   AddXMLFooter();
   objXMLDocument.loadXML(strXML); // Parse the XML.
   if ((objXMLDocument.parseError.errorCode == 0))
   {
     AddHTMLHeader(HTML_CSS_FILE);
     AddHTMLBody(objXMLDocument.documentElement); // Builds HTML string.
     AddHTMLFooter();
     Response.Write(strHTML);
   }
   else
   {
     OutputParserError(objXMLDocument);
   }
   break;

 case DISPLAY_CSS:  // XML with CSS formatting
 // This is supposed to work - but there is a problem.
 // Whereas CSS in XML works (for the most part) in a static XML
 // document, CSS is ignored if it is used in XML document
 // dynamically generated by ASP.
   AddXMLHeader("");
   strXML = strXML + strXMLBody;
   AddXMLFooter();
   objXMLDocument.loadXML(strXML); // Parse the XML.
   if ((objXMLDocument.parseError.errorCode == 0))
   {
     MassageXML(objXMLDocument); // Massage XML for CSS.
     AddXMLHeader(XML_CSS_FILE);
     AddXMLBody(objXMLDocument.documentElement); // Builds XML string.
     AddXMLFooter();
     Response.Write(strXML);
   }
   else
   {
     OutputParserError(objXMLDocument);
   }
   break;

 case DISPLAY_TEXT:
 // Plain text HTML.
   AddXMLHeader("");
   strXML = strXML + strXMLBody;
   AddXMLFooter();
   AddHTMLHeader("");
   strHTML = strHTML + "<plaintext>" + strXML + "</plaintext>";
   AddHTMLFooter();
   Response.Write(strHTML);
   break;

 case DISPLAY_XSL:
 // XML formatted with XSL.
   AddXMLHeader(XML_XSL_FILE);
   strXML = strXML + strXMLBody;
   AddXMLFooter();
   Response.Write(strXML);
   break;

 case DISPLAY_XSL_SERVER:
 // XML formatted with XSL, transformed at server.
   strXSLPath = GetCurrPath() + XML_XSL_FILE;
   objXSLDocument = new ActiveXObject("microsoft.xmldom");
   objXSLDocument.load(strXSLPath);  // Parse the XSL file.
   if ((objXSLDocument.parseError.errorCode == 0) {
     AddXMLHeader("");
     strXML = strXML + strXMLBody;
     AddXMLFooter();
     objXMLDocument.loadXML(strXML); // Parse the XML string.
     if (objXMLDocument.parseError.errorCode == 0) {
       // Transform XML with XSL, creating HTML.
       Response.Write(objXMLDocument.transformNode(
                      objXSLDocument.documentElement));
     }
     else {
       OutputParserError(objXMLDocument);
     }
   }
   else {
     OutputParserError(objXSLDocument);
   }
   break;

 default: // Raw XML
   AddXMLHeader("");
   strXML = strXML + strXMLBody;
   AddXMLFooter();
   Response.Write(strXML);
   break;
 }

 %>

Figure 10   CSS File for HTML Display Formatting


/*
    Cascading Style Sheet for computer data.
    This file is used with the HTML-formatted result.
*/

.VendorLine             {font-family:Arial; font-size:28; color:blue;}

.ModelLine              {font-family:Arial; font-size:16; font-style:italic;
                         font-weight:bold; color:navy;}

.DataTable              {}

.DataLine               {font-family:Arial; font-size:14;
                         color:black; border:outset;}

.ErrorMessage           {font-family:Arial; font-size:16; color:maroon;}

Figure 11   CSS File for XML Display Formatting


/*
    Cascading Style Sheet for computer data.
    This file is used for the XML result.
*/

computer        {display:block; margin:15px;}
vendor          {display:block; font-family:Arial; font-size:28; color:blue;}
description     {display:block; font-family:Arial; font-size:16;
                         font-style:italic; font-weight:bold; color:navy;}
price           {display:inline; font-family:Arial; font-size:14; color:black;}
disk            {display:inline; font-family:Arial; font-size:14;
                         color:black;}
monitor         {display:inline; font-family:Arial; font-size:14; color:black;}
cpu             {display:inline; font-family:Arial; font-size:14; color:black;}
ram             {display:inline; font-family:Arial; font-size:14; color:black;}
operatingsystem {display:inline; font-family:Arial; font-size:14; color:black;}
removabledisk   {display:inline; font-family:Arial; font-size:14; color:black;}
inputoutput     {display:inline; font-family:Arial; font-size:14; color:black;}
.ErrorMessage   {font-family:Arial; font-size:16; color:maroon;}

Figure 12   Transforming XML with XSL


<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"
xmlns="http://www.w3.org/TR/REC-html40" result-ns="">

<!--
    Extended Style Sheet for computer data.
    This file is used with the XML result.
-->

<xsl:template match="/">
  <HTML>
    <HEAD>
      <!-- We will be setting up the table and format in XSL,
           but use CSS for other stuff like colors, font, etc. -->
      <LINK REL="STYLESHEET" TYPE="text/css" HREF="compuser.css"/>
    </HEAD>
    <BODY>
      <A HREF="compuser.htm">Return to Computer Finder</A>
      <HR/>
      <TABLE CLASS="DataTable">
        <!-- Cast price element as number, so sorted numerically. -->
        <xsl:apply-templates select="computers/computer" order-
        by="+number(@price)"/>
      </TABLE>
    </BODY>
  </HTML>
</xsl:template>

<xsl:template match="computers/computer">
  <TR>
    <TD COLSPAN="10" CLASS="VendorLine">
      <xsl:apply-templates select="vendor"/>
    </TD>
  </TR>
  <TR>
    <TD COLSPAN="10" CLASS="ModelLine">
      <xsl:value-of select="@model"/>:
      <xsl:value-of select="description"/>
    </TD>
  </TR>
  <TR>
    <TD CLASS="DataLine">$<xsl:value-of select="@price"/></TD>
    <TD CLASS="DataLine"><xsl:apply-templates select="disk"/></TD>
    <TD CLASS="DataLine"><xsl:apply-templates select="monitor"/></TD>
    <TD CLASS="DataLine"><xsl:apply-templates select="cpu"/></TD>
    <TD CLASS="DataLine"><xsl:apply-templates select="ram"/></TD>
    <TD CLASS="DataLine"><xsl:apply-templates select="operatingsystem"/></TD>
    <TD CLASS="DataLine"><xsl:apply-templates select="removabledisk"/></TD>
    <xsl:apply-templates select="inputoutput"/>
  </TR>
</xsl:template>

<xsl:template match="vendor">
  <xsl:value-of select="@name"/>:
  <A>
    <xsl:attribute name="HREF"><xsl:value-of select="@website"/></xsl:attribute>
    <xsl:value-of select="@website"/>
  </A>
</xsl:template>

<xsl:template match="disk">
  <xsl:value-of select="@size"/> GB Disk
</xsl:template>

<xsl:template match="monitor">
  <xsl:value-of select="@size"/> in Monitor
</xsl:template>

<xsl:template match="cpu">
  <xsl:value-of select="@brand"/>:
  <xsl:value-of select="@speed"/> Mhz
</xsl:template>

<xsl:template match="ram">
  <xsl:value-of select="@size"/> MB RAM
</xsl:template>

<xsl:template match="operatingsystem">
  <xsl:value-of select="@name"/>
</xsl:template>

<xsl:template match="removabledisk">
  <xsl:value-of select="@type"/>
</xsl:template>

<xsl:template match="inputoutput">
  <TD CLASS="DataLine"><xsl:apply-templates select="mouse"/></TD>
  <TD CLASS="DataLine"><xsl:apply-templates select="speakers"/></TD>
  <TD CLASS="DataLine"><xsl:apply-templates select="joystick"/></TD>
</xsl:template>

<xsl:template match="mouse">
  Mouse
</xsl:template>

<xsl:template match="speakers">
  Speakers
</xsl:template>

<xsl:template match="joystick">
  Joystick
</xsl:template>

</xsl:stylesheet>