home *** CD-ROM | disk | FTP | other *** search
/ Dynamic HTML in Action / Dynamicke-HTML-v-akci-covermount.bin / XML / PARSER / XMLINST.EXE / classes / com / ms / xml / dso / XMLDSO.java < prev    next >
Encoding:
Java Source  |  1997-10-30  |  35.5 KB  |  1,162 lines

  1. package com.ms.xml.dso;
  2.  
  3. import java.util.*;
  4. import java.applet.*;
  5. import java.awt.*;
  6. import java.net.*;
  7. import java.io.*;
  8.  
  9. import netscape.javascript.JSObject;
  10. import com.ms.osp.*;
  11. import com.ms.xml.parser.*;
  12. import com.ms.xml.om.*;
  13. import com.ms.xml.util.Name;
  14. import com.ms.xml.util.XMLOutputStream;
  15. import com.ms.xml.util.Attribute;
  16. import com.ms.xml.util.StringInputStream;
  17.  
  18. /**
  19.  * An XMLDSO is an applet that can be used in an APPLET tag to
  20.  * load XML files and provide that data to for data binding.
  21.  * See IE 4.0 documentation for more information on data binding.
  22.  *
  23.  * @version 1.0, 8/14/97
  24.  * @see Document
  25.  * @see Element
  26.  */
  27. public class XMLDSO extends Applet
  28. {
  29.     /**
  30.      * Construct a new XMLDSO object with an empty document.
  31.      */
  32.     public XMLDSO()
  33.     {
  34.         document = new Document(); 
  35.         inlineXML = false;
  36.         loaded = false;
  37.     }
  38.  
  39.     /**
  40.      * The init method looks for a URL PARAM and loads this
  41.      * url if specified.  Otherwise it looks for inline XML 
  42.      * from inside the APPLET tag that created this XMLDSO
  43.      * object.  It does this using the ID parameter of the 
  44.      * APPLET tag to lookup the actual applet object in 
  45.      * the page, using JSObject, an then getting the altHtml 
  46.      * member of this object.   Note: it is ok to not have
  47.      * a URL or any inline XML.
  48.      */
  49.     public void init()
  50.     {   
  51.         super.init();
  52.         String arg = getParameter("URL");
  53.         if (arg != null && arg.length() > 0) {
  54.             load(arg);
  55.         }
  56.         else
  57.         {
  58.             loaded = true;
  59.             // Or we can get the XML data inline from inside the
  60.             // <APPLET> tag using the JavaScript object model.
  61.             arg = getParameter("ID");
  62.             if (arg != null && arg.length() > 0) 
  63.             {
  64.                 JSObject appl = null;
  65.                 try {
  66.                     appl = (JSObject)((JSObject)JSObject.getWindow(this).getMember("document")).getMember(arg);
  67.                 } catch (Exception e) {
  68.                     setError("Error finding <APPLET> with ID=" + arg + ": " + e.toString());
  69.                     return;
  70.                 }
  71.                 if (appl != null)
  72.                 {
  73.                     String data = (String)appl.getMember("altHtml");
  74.                     if (data != null && data.length() > 0)
  75.                     {
  76.                         document = parseXML(data);
  77.                         inlineXML = true;
  78.                     }
  79.                 }
  80.                 else
  81.                 {
  82.                     setError("Error finding <APPLET> with ID=" + arg);
  83.                 }
  84.             } else {
  85.                 setError("No PARAM with NAME=URL was found, " +
  86.                     "so perhaps you specified the XML inside the APPET tag " +
  87.                     "but in order for this to work you must also specify " +
  88.                     "<PARAM NAME=\"APPLET\" VALUE=\"xmldso\">" +
  89.                     " where the value matches the ID of your APPLET tag.");
  90.             }
  91.         }
  92.         updateSchema();
  93.     }
  94.  
  95.     /**
  96.      * This method is called whenever the document is changed,
  97.      * that is, from the load and setroot methods.  You can
  98.      * also call this method if you have manually changed the 
  99.      * document or the SCHEMA <PARAM>.
  100.      */
  101.     public void updateSchema()
  102.     {
  103.         schema = new Document();
  104.         // the SCHEMA <PARAM> is used to define a subset of data
  105.         // that is visible through the DSO.  This method can be
  106.         // called to update the schema when the PARAM is changed
  107.         // programatically or the document has changed through
  108.         // scripting and the schema needs to be updated.
  109.         String arg = getParameter("SCHEMA");
  110.         if (arg != null && arg.length() > 0) {
  111.             schema = parseXML(arg);
  112.             schemaRoot = schema.getRoot();
  113.         } else {
  114.             // Generate schema from the data.
  115.             if (document != null && document.getRoot() != null)
  116.             {
  117.                 Element root = document.getRoot();
  118.                 SchemaNode n = new SchemaNode(schema, schema, root.getTagName());
  119.                 generateSchema(root, n);
  120.                 // remember only first entry below root
  121.                 ElementEnumeration iter = new ElementEnumeration(schema.getRoot(), 
  122.                     XMLRowsetProvider.nameROWSET,Element.ELEMENT);    
  123.                 schemaRoot = (Element)iter.nextElement();
  124.             }
  125.         }
  126.         notifyListeners();        
  127.     }
  128.  
  129.     public Document parseXML(String xml)
  130.     {
  131.         Document document = new Document();
  132.         if (xml == null)
  133.             return document;
  134.  
  135.         try {
  136.             document.load(new StringInputStream(xml));
  137.         } 
  138.         catch (Exception e)
  139.         {
  140.             setError("Caught exception parsing given XML.  " + 
  141.                 e.toString());
  142.         }
  143.         return document; // but return what we have anyway.
  144.     }
  145.  
  146.     /**
  147.      * Parse and Process the given update gram
  148.      */
  149.     public void handleUpdateString(String xml)
  150.     {
  151.         Document d = parseXML(xml);
  152.         handleUpdate(d.getRoot());
  153.     }
  154.  
  155.     /**
  156.      * Process the given update gram
  157.      */
  158.     public void handleUpdate(Element root)
  159.     {
  160.         if (document == null) {
  161.             document = new Document();
  162.             document.addChild(root,null);
  163.             updateSchema();
  164.             return;
  165.         }
  166.  
  167.         try {
  168.             Element docRoot = document.getRoot();
  169.             for (ElementEnumeration en = new ElementEnumeration(root);
  170.                     en.hasMoreElements();)
  171.             {
  172.                 Element action = (Element)en.nextElement();
  173.                 updateTree(docRoot,action);
  174.             }
  175.         } catch (Exception e) {
  176.             setError("HandleUpdate error: " + e.toString());
  177.         }
  178.     }
  179.  
  180.     boolean updateTree(Element node1, Element node2)
  181.     {
  182.         int type = node2.getType();
  183.         if (type == Element.COMMENT || type == Element.PI || type == Element.WHITESPACE)
  184.             return true;
  185.  
  186.         if (type == Element.PCDATA || type == Element.ENTITYREF)
  187.         {
  188.             if (node1.getText().equals(node2.getText()))
  189.                 return true;
  190.             else
  191.                 return false;
  192.         } 
  193.  
  194.         String action = (String)node2.getAttribute("UPDATE-ACTION");
  195.         if (action != null && (action.equalsIgnoreCase("APPEND") || 
  196.                 action.equalsIgnoreCase("INSERT"))) 
  197.         {
  198.             {
  199.                 node2.removeAttribute("UPDATE-ACTION");
  200.                 boolean append = action.equalsIgnoreCase("APPEND");
  201.                 if (append)
  202.                     node1.addChild(node2,null);
  203.                 else {
  204.                     node1.addChild(node2,0,0);
  205.                 }
  206.                 notifyNewRow(node1, append);
  207.                 return true;                
  208.             } 
  209.         }
  210.         int num = node2.numElements();
  211.         if (num > 0 )
  212.         {
  213.             int row = 0;
  214.             for (ElementEnumeration en = new ElementEnumeration(node1,node2.getTagName(),Element.ELEMENT);
  215.                 en.hasMoreElements();)
  216.             {
  217.                 row++;
  218.                 Element element = (Element)en.nextElement();
  219.  
  220.                 // make sure we match all the child patterns.                    
  221.                 Vector actions = new Vector();
  222.                 boolean match = false;
  223.                 for (ElementEnumeration en2 = new ElementEnumeration(node2);
  224.                         en2.hasMoreElements();)
  225.                 {
  226.                     Element pattern = (Element)en2.nextElement();
  227.                     String cmd = (String)pattern.getAttribute("UPDATE-ACTION");
  228.                     if (cmd == null || cmd.equalsIgnoreCase("APPEND") ||
  229.                         cmd.equalsIgnoreCase("INSERT") ||
  230.                         cmd.equalsIgnoreCase("REMOVE")) {
  231.                         match = updateTree(element,pattern);
  232.                         if (! match)
  233.                             break; // found a non-match
  234.  
  235.                     } else {
  236.                         actions.addElement(pattern);
  237.                     }
  238.                 }
  239.                 if (! match)
  240.                     continue;
  241.  
  242.                 if (action != null && action.equalsIgnoreCase("REMOVE"))
  243.                 {
  244.                     node1.removeChild(element);
  245.                     notifyRemovedRow(node1,row);
  246.                     return true;
  247.                 }
  248.  
  249.                 // since we matched let's process the actions.
  250.                 for (Enumeration an = actions.elements(); an.hasMoreElements();)
  251.                 {
  252.                     Element actElement = (Element)an.nextElement();
  253.                     String cmd = (String)actElement.getAttribute("UPDATE-ACTION");
  254.                     actElement.removeAttribute("UPDATE-ACTION");
  255.                     if (cmd.equalsIgnoreCase("REPLACE") )
  256.                     {
  257.                         ElementEnumeration ee = new ElementEnumeration(element,
  258.                                 actElement.getTagName(),Element.ELEMENT);
  259.                         Element oldChild = (Element)ee.nextElement();
  260.                                 
  261.                         element.addChild(actElement,oldChild);
  262.                         if (oldChild != null)
  263.                             element.removeChild(oldChild);
  264.                         element.setParent(node1);
  265.                         notifyCellChanged(row, element, actElement);                        
  266.                     } 
  267.                 }
  268.                 return true;
  269.             }
  270.         }
  271.         return false;
  272.     }
  273.  
  274.     int getColumn(Element schema, Element element)
  275.     {
  276.         int col = 0;
  277.         String tagname = element.getTagName().toString();            
  278.         for (Enumeration en = schema.getElements(); en.hasMoreElements(); ) 
  279.         {
  280.             col++;
  281.             Element e = (Element)en.nextElement();
  282.             String n1 = e.getAttribute("NAME").toString();
  283.             if (n1.equals(tagname))
  284.                 return col;
  285.         }
  286.         setError(tagname + " not found in schema.");
  287.                     
  288.         return 0;
  289.     }
  290.  
  291.     /**
  292.      * Function to provide the OLE-DB simple provider interface to callers.
  293.      * The qualifier parameter is ignored at this point, but is available
  294.      * to allow the applet to serve up more than one data set
  295.      */
  296.     public OLEDBSimpleProvider msDataSourceObject(String qualifier)
  297.     {
  298.         removeProvider(myProvider);
  299.         if (document != null && document.getRoot() != null && schemaRoot != null) {          
  300.             // This is a smarter provider that supports a hierarchy
  301.             // of XMLRowsetProviders based on the given schema information.
  302.             myProvider = new XMLRowsetProvider (document.getRoot(), schemaRoot, (ElementFactory)document, null);
  303.         } else {
  304.             myProvider = null;
  305.         }
  306.         addProvider(myProvider);
  307.         return myProvider;
  308.     }
  309.  
  310.     /**
  311.      * This is a standard method for DSO's.
  312.      */
  313.     public void addDataSourceListener(DataSourceListener  listener)
  314.         throws java.util.TooManyListenersException
  315.     {
  316.         if (myDSL != null) {
  317.             com.ms.com.ComLib.release(myDSL);
  318.         }
  319.         myDSL = listener;
  320.     }
  321.  
  322.     /**
  323.      * This is a standard method for DSO's.
  324.      */
  325.     public void removeDataSourceListener( DataSourceListener   listener)
  326.     {
  327.         // BUGBUG: Shouldn't have to call release here. This is a
  328.         //         bug in the VM implementation.
  329.         if (myDSL != null) {
  330.             com.ms.com.ComLib.release(myDSL);
  331.         }
  332.         myDSL = null;
  333.     }
  334.  
  335.     void removeProvider(XMLRowsetProvider p)
  336.     {
  337.         if (p != null)
  338.             providers.removeElement(p);
  339.     }
  340.  
  341.     void addProvider(XMLRowsetProvider p)
  342.     {
  343.         if (p != null) {
  344.             providers.addElement(p);
  345.         }
  346.     }
  347.  
  348.     /**
  349.      * Notify all DSO's that are bound to this row that the given cell has 
  350.      * been modified.
  351.      */
  352.     void notifyCellChanged(int row, Element rowElement, Element modifiedElement)
  353.     {
  354.         long count = 0;
  355.         for (Enumeration en = providers.elements(); en.hasMoreElements(); )
  356.         {
  357.             XMLRowsetProvider provider = (XMLRowsetProvider)en.nextElement();
  358.             Element node = rowElement;
  359.             while (node != null) {
  360.                 XMLRowsetProvider child = provider.findChildProvider(node);
  361.                 if (child != null)
  362.                 {
  363.                     count++;
  364.                     OLEDBSimpleProviderListener listener = child.getListener();
  365.                     if (listener != null)
  366.                     {
  367.                         int col = getColumn(child.getSchema(), modifiedElement);
  368.                         if (col != 0) {
  369.                             listener.aboutToChangeCell(row,col);
  370.                             listener.cellChanged(row,col);
  371.                             return;
  372.                         }
  373.                     }
  374.                 }
  375.                 node = node.getParent();
  376.             }
  377.         }                            
  378.     }
  379.  
  380.     void notifyNewRow(Element node, boolean append)
  381.     {
  382.         for (Enumeration en = providers.elements(); en.hasMoreElements(); )
  383.         {
  384.             XMLRowsetProvider provider = (XMLRowsetProvider)en.nextElement();
  385.             XMLRowsetProvider child = provider.findChildProvider(node);
  386.             if (child != null)
  387.             {
  388.                 OLEDBSimpleProviderListener listener = child.getListener();
  389.                 if (listener != null)
  390.                 {
  391.                     int row = (append) ? provider.getRowCount() : 1;
  392.                     try {
  393.                         listener.aboutToInsertRows(row,1);
  394.                     } catch (com.ms.com.ComSuccessException e)
  395.                     { }
  396.                     listener.insertedRows(row,1);
  397.                 }
  398.             }
  399.         }
  400.     }
  401.  
  402.     void notifyRemovedRow(Element node, int row)
  403.     {
  404.         for (Enumeration en = providers.elements(); en.hasMoreElements(); )
  405.         {
  406.             XMLRowsetProvider provider = (XMLRowsetProvider)en.nextElement();
  407.             XMLRowsetProvider child = provider.findChildProvider(node);
  408.             if (child != null)
  409.             {
  410.                 OLEDBSimpleProviderListener listener = child.getListener();
  411.                 if (listener != null)
  412.                 {
  413.                     try {
  414.                         listener.aboutToDeleteRows(row,1);
  415.                     } catch (com.ms.com.ComSuccessException e)
  416.                     { }
  417.                     listener.deletedRows(row,1);
  418.                 }
  419.             }
  420.         }
  421.     }
  422.  
  423.  
  424.     /**
  425.      * Notify current DataSouceListener that data has changed.
  426.      */
  427.     public void notifyListeners()
  428.     {
  429.         if (myDSL != null) {
  430.             try {
  431.                 myDSL.dataMemberChanged("");
  432.             } 
  433.             catch (Exception e)
  434.             {
  435.                 setError("Error notifying data members changed: " + e.toString());
  436.             }
  437.         }
  438.     }
  439.  
  440.     /**
  441.      * Return the loaded document.
  442.      */
  443.     public Object getDocument()
  444.     {
  445.         // return document to allow scripting
  446.         return document;
  447.     }
  448.  
  449.     /**
  450.      * This method is called to set the root of the document
  451.      * for this XMLDSO object.  This is useful when you want to
  452.      * link multiple DSO's together.
  453.      */
  454.     public void setRoot(Element e)
  455.     {
  456.         document = new Document();
  457.         document.addChild(e,null);
  458.         updateSchema();
  459.     }
  460.  
  461.     public void clear()
  462.     {
  463.         document = new Document();
  464.         updateSchema();
  465.     }
  466.  
  467.     /**
  468.      * Reload the document using the given URL.  Relative URL's
  469.      * are resolved using the HTML document as a base URL.
  470.      */
  471.     public void load(String arg)
  472.     {
  473.         // Resolve this URL using the Document's URL as the base URL.
  474.         URL base = getDocumentBase();
  475.  
  476.         // We can get the XML data from a remote URL
  477.         loaded = false;
  478.         try {
  479.             if (base == null) {
  480.                 url = new URL(arg);
  481.             } else {
  482.                 url = new URL(base,arg);
  483.             }
  484.             document.load(url.toString());
  485.             loaded = true;
  486.         } catch (Exception e) {
  487.             setError("Error loading XML document '" + arg + "'.  " + e.toString());            
  488.         }
  489.         if (loaded && schema != null) {
  490.             updateSchema();
  491.         }
  492.     }
  493.  
  494.     public void asyncLoad(String arg, String callback)
  495.     {
  496.         document = null;
  497.         try {
  498.             URL url = new URL(getDocumentBase(),arg);
  499.             document = new Document();
  500.             JSObject win = (JSObject)JSObject.getWindow(this);
  501.             XMLParserThread t = new XMLParserThread(url, win, callback);
  502.             t.start();
  503.         } catch (Exception e) {
  504.             setError("Error loading document '" + arg + "'.  " + e.toString());
  505.         }
  506.     }
  507.  
  508.     /**
  509.      * Return the XML for the loaded document as a big string.
  510.      * Pass in the formatting style. 0=default, 1=pretty, 2=compact.
  511.      */
  512.     public Object getXML(int style)
  513.     {
  514.         // return XML as long string
  515.         if (document != null) 
  516.         {
  517.             ByteArrayOutputStream out = new ByteArrayOutputStream();
  518.             try {
  519.                 XMLOutputStream s = new XMLOutputStream(out);
  520.                 s.setOutputStyle(style);
  521.                 document.save(s);
  522.             } catch (Exception e) {
  523.                 setError(e.toString());
  524.             }
  525.             return out.toString();
  526.         }
  527.         return null;
  528.     }
  529.  
  530.     /**
  531.      * Return string containing last error encountered by the XMLDSO.
  532.      */
  533.     public String getError()
  534.     {
  535.         return error;
  536.     }
  537.  
  538.     /**
  539.      * Save the schema to the given file.
  540.      */
  541.     public void saveSchema(String filename)
  542.     {
  543.         if (schema != null)
  544.         {
  545.             try
  546.             {
  547.                 FileOutputStream out = new FileOutputStream(filename);
  548.                 schema.save(new XMLOutputStream(out));
  549.             }
  550.             catch(Exception e)
  551.             {
  552.                 setError(e.toString());
  553.             }
  554.         }
  555.     }
  556.  
  557.     /**
  558.      * Return the schema as a string.
  559.      */
  560.     public Object getSchema(int style)
  561.     {
  562.         // return Schema as long string
  563.         if (schema != null) 
  564.         {
  565.             ByteArrayOutputStream out = new ByteArrayOutputStream();
  566.             try {
  567.                 XMLOutputStream xout = new XMLOutputStream(out);
  568.                 xout.setOutputStyle(style);
  569.                 schema.save(xout);
  570.             } catch (Exception e) {
  571.                 setError(e.toString());
  572.             }
  573.             return out.toString();
  574.         }
  575.         return null;
  576.     }
  577.  
  578.     /**
  579.      * Save the XML document to the given file.
  580.      */
  581.     public void save(String filename)
  582.     {
  583.         if (document != null)
  584.         {
  585.             try
  586.             {
  587.                 FileOutputStream out = new FileOutputStream(filename);
  588.                 document.save(document.createOutputStream(out));
  589.             }
  590.             catch(Exception e)
  591.             {
  592.                 setError(e.toString());
  593.             }
  594.         }
  595.     }
  596.  
  597.     private void setError(String e)
  598.     {
  599.         error = e;
  600.         repaint();
  601.     }
  602.  
  603.     void generateSchema(Element source, SchemaNode n)
  604.     {
  605. //        System.out.println("Processing node:" + source.getTagName());
  606.         // go thru the children of source and generate schema
  607.         // nodes added later to parent.
  608.         for (ElementEnumeration en = new ElementEnumeration(source); en.hasMoreElements(); )
  609.         {
  610.             Element e = (Element)en.nextElement();
  611.             int t = e.getType();
  612.             Name name = e.getTagName();
  613. //            System.out.println("Found: " + name + "," + t);
  614.             if (t == Element.ELEMENT)
  615.             {
  616.                 // now we know that parent is a rowset...
  617.                 SchemaNode sn = n.setRow(name);
  618.                 generateSchema(e, sn);
  619.             }
  620.         }
  621.  
  622.         // if children didn't create element it means it is a column...
  623.         // (at least for now.  If we find that it is supposed to be
  624.         // a ROWSET later in the document, then setRow() will fix it up).
  625.         if (n.e == null)
  626.         {
  627.             n.createElement(false);
  628.         }
  629.     }
  630.  
  631.     /**
  632.      * When the APPLET containing this XMLDSO is given a non-zero bounds
  633.      * this paint methods displays diagnostic information that helps the
  634.      * page author debug the page.
  635.      */
  636.     public void paint(Graphics g)
  637.     {
  638.         Dimension d =  size();
  639.         if (d.width > 0 && d.height > 0)
  640.         {
  641.             if (error == null) {
  642.                 g.setColor(Color.green);
  643.             } else {
  644.                 g.setColor(Color.red);
  645.             }
  646.             g.fillRect(0,0,d.width,d.height);
  647.             String text = error;
  648.             if (text == null) {
  649.                 if (url != null) {
  650.                     text = "Successfully loaded XML from \"" + url.toString() + "\"";
  651.                 } else if (inlineXML) {
  652.                     text = "Successfully loaded inline XML";
  653.                 } else if (document.getRoot() != null) {
  654.                     text = "Successfully loaded document.";
  655.                 } else {
  656.                     text = "Empty";
  657.                 }
  658.             }
  659.             g.setColor(Color.black);
  660.             drawText(g,text,5,5,d.width-10,true,0);
  661.         }
  662.     }
  663.  
  664.     /**
  665.      * Draw a text string within the given bounds.
  666.      */
  667.     private int drawText(Graphics g, String text, int x, int y, int max, 
  668.                         boolean skipWhiteSpace, int length)
  669.     {
  670.         if( text == null || text.length() == 0 )
  671.             return y;
  672.         if (max < 5) // make sure we have room to draw text.
  673.             return y;
  674.         int i = 0;
  675.         int len;
  676.         int w = 0;
  677.  
  678.         if( length == 0 )
  679.             len = text.length();
  680.         else
  681.             len = length;
  682.         
  683.         // skip leading white space.
  684.         while (i < len && skipWhiteSpace && isWhiteSpace(text.charAt(i)))
  685.             i++;
  686.         
  687.         FontMetrics fm = g.getFontMetrics();
  688.         int j = i;
  689.         int k = i;
  690.         while (i < len) {
  691.             char ch = text.charAt(i);
  692.             int cw = fm.charWidth(ch);
  693.             w += cw;
  694.             if (w > max || ch == '\n') {
  695.                 if( ch == '\n' && text.charAt(i-1) == 0x0D )
  696.                     j = i-1;
  697.                 else if (k == j)
  698.                     j = i;
  699.                 String sub = text.substring(k,j);
  700.                 g.drawString(sub,x,y+fm.getMaxAscent());
  701.                 y += fm.getHeight();
  702.                 // skip white space.
  703.                 if( ch == '\n' && text.charAt(i-1) == 0x0D )
  704.                     j = i+1;
  705.                 while (skipWhiteSpace && j < len && isWhiteSpace(text.charAt(j)))
  706.                     j++;
  707.                 i = j;
  708.                 k = j;
  709.                 w = 0;
  710.             } else {
  711.                 if (skipWhiteSpace && isWhiteSpace(ch)) {
  712.                     j = i;
  713.                 }
  714.                 i++;
  715.             }
  716.         }
  717.         String remaining = text.substring(k);
  718.         g.drawString(remaining,x,y+fm.getMaxAscent());
  719.         return y;
  720.     }
  721.  
  722.     private boolean isWhiteSpace(char ch)
  723.     {
  724.         return Character.isSpace(ch) || ch == 13;
  725.     }
  726.  
  727.     private XMLRowsetProvider  myProvider;
  728.     private DataSourceListener myDSL;
  729.     private Document           document;
  730.     private Document           schema;
  731.     private Element                schemaRoot;
  732.     private String              error;
  733.     private URL                 url;
  734.     private boolean             loaded;
  735.     private boolean             inlineXML;
  736.     static Vector providers = new Vector();
  737. }
  738.  
  739. //------------------------------------------------------------------
  740. class SchemaNode
  741. {
  742.     Element e;
  743.     Hashtable rows;
  744.     Name name;
  745.     Element parent;
  746.     ElementFactory factory;
  747.  
  748.     SchemaNode(Element parent, ElementFactory factory, Name name)
  749.     {
  750.         this.parent = parent;
  751.         this.name = name;
  752.         this.factory = factory;
  753.     }
  754.  
  755.     void createElement(boolean rowset)
  756.     {
  757.         if (rowset)
  758.         {
  759.             e = factory.createElement(Element.ELEMENT, XMLRowsetProvider.nameROWSET);
  760.         }
  761.         else
  762.         {
  763.             e = factory.createElement(Element.ELEMENT, XMLRowsetProvider.nameCOLUMN);
  764.         }
  765.         parent.addChild(e, null);
  766.         e.setAttribute(XMLRowsetProvider.nameNAME, name.toString());
  767.     }
  768.  
  769.     SchemaNode setRow(Name name)
  770.     {
  771.         if (e == null)
  772.         {
  773.             createElement(true);
  774.         } 
  775.         else if (e.getTagName() != XMLRowsetProvider.nameROWSET) 
  776.         {
  777.             // We have now discovered that node is supposed to
  778.             // be a ROWSET not a COLUMN.
  779.             parent.removeChild(e);
  780.             createElement(true);
  781.         }
  782.  
  783.         SchemaNode sn = getRow(name);
  784.         // add new subnode if new or was added as text
  785.         if (sn == null)
  786.         {
  787.             sn = new SchemaNode(e, factory, name);
  788.             addRow(name, sn);
  789.         }
  790.         return sn;
  791.     }
  792.  
  793.     void addRow(Name name, SchemaNode n)
  794.     {
  795.         if (rows == null) 
  796.             rows = new Hashtable(13);
  797.         rows.put(name, n);
  798.     }
  799.  
  800.     SchemaNode getRow(Name n)
  801.     {
  802.         if (rows == null)
  803.             return null;
  804.         return (SchemaNode)rows.get(n);
  805.     }
  806. }
  807.  
  808. //------------------------------------------------------------------
  809. class XMLRowsetProvider  implements OLEDBSimpleProvider  
  810. {    
  811.     public XMLRowsetProvider (Element e, Element schema, ElementFactory f, XMLRowsetProvider parent)
  812.     {
  813.         root = e;
  814.         this.schema = schema;
  815.         this.parent = parent;
  816.         this.factory = f;
  817.         // This provider iterates over nodes in root that match
  818.         // ROWSET name defined in schema.
  819.         rowset = Name.create((String)schema.getAttribute(nameNAME));
  820.         resetIterator();
  821.     }
  822.  
  823.     public Element getSchema()
  824.     {
  825.         return schema;
  826.     }
  827.  
  828.     void resetIterator()
  829.     {
  830.         iter = new ElementEnumeration(root,rowset,Element.ELEMENT);
  831.         row = (Element)iter.nextElement();
  832.         rowindex = 0;
  833.     }
  834.  
  835.     public int getRowCount() 
  836.     {
  837.         // Return number of children in root that match the
  838.         // ROWSET name.
  839.         int result = 0;
  840.         iter.reset();
  841.         while (iter.nextElement() != null)
  842.             result++;
  843.         iter.reset();
  844.         row = (Element)iter.nextElement();
  845.         rowindex = 0;
  846.         return result;
  847.     }
  848.  
  849.     public int getEstimatedRows() 
  850.     {
  851.         return getRowCount();
  852.     }
  853.  
  854.     public int getColumnCount() 
  855.     {
  856.         // Simply return the number of elements in the schema.
  857.         int columns = schema.getChildren().getLength();
  858.         return columns;
  859.     }
  860.  
  861.     public int getRWStatus(int iRow,int iColumn) 
  862.     {
  863.         return 1;
  864.     }
  865.  
  866.     public void addOLEDBSimpleProviderListener(OLEDBSimpleProviderListener l)
  867.     {
  868.         if (listener != null) {
  869.             com.ms.com.ComLib.release(listener);
  870.         }
  871.         listener = l;
  872.     }
  873.  
  874.     public void removeOLEDBSimpleProviderListener(OLEDBSimpleProviderListener l)
  875.     {
  876.         if (listener != null) {
  877.             com.ms.com.ComLib.release(listener);
  878.         }
  879.         listener = null;
  880.     }
  881.  
  882.     public int find(int iStartRow, int iColumn, Object varSearchVal,
  883.                     int findFlags, int compType)
  884.     {
  885.         return 0;
  886.     }
  887.  
  888.     public int deleteRows(int iRow,int cRows)
  889.     {
  890.         try {
  891.             if (listener != null) listener.aboutToDeleteRows(iRow,cRows);
  892.         } catch (com.ms.com.ComSuccessException e)
  893.         { }
  894.         int result = 0;
  895.         for (int i = iRow; i < iRow+cRows; i++) {
  896.             Element row = getRow(iRow);
  897.             if (row != null) {
  898.                 result++;
  899.                 root.removeChild(row);
  900.                 removeChildProvider(row);
  901.             }
  902.         }
  903.         resetIterator();        
  904.         if (listener != null) listener.deletedRows(iRow,cRows);
  905.         return result;
  906.     }
  907.  
  908.     public int insertRows(int iRow,int cRows)
  909.     {
  910.         try {
  911.             if (listener != null) listener.aboutToInsertRows(iRow,cRows);
  912.         } catch (com.ms.com.ComSuccessException e) 
  913.         { }
  914.  
  915.         Name name = Name.create(schema.getAttribute("NAME").toString());
  916.         for (int i = iRow; i < iRow+cRows; i++) {
  917.             Element newRow = factory.createElement(Element.ELEMENT,name);
  918.             Element previousRow = root.getChildren().getChild(i);
  919.             root.addChild(newRow,previousRow);            
  920.         }
  921.         resetIterator();
  922.         if (listener != null) listener.insertedRows(iRow,cRows);
  923.         return cRows;
  924.     }
  925.  
  926.     public Object getVariant(int iRow, int iColumn,int formatType )  
  927.     {
  928.         Object retVal = null;
  929.         if (iRow == 0)
  930.         {
  931.             // return the column information.
  932.             if (iColumn <= getColumnCount()) {
  933.                 Element e = schema.getChildren().getChild(iColumn-1);
  934.                 String name = (String)e.getAttribute(nameNAME);
  935.                 if (name != null) {
  936.                     if (e.getTagName() != nameCOLUMN) {
  937.                         // HACK to mark the column as rowset for the OSP...
  938.                         retVal = "^" + name + "^";
  939.                     } else {
  940.                         retVal = name;
  941.                     }
  942.                 }
  943.             }
  944.         }
  945.         else
  946.         {
  947.             getRow(iRow); // update current row.
  948.             retVal = getColumn(row,iColumn-1);
  949.         }
  950.         return retVal;
  951.     }
  952.  
  953.     Element getRow(int iRow)
  954.     {
  955.         // The current row is cached for performance reasons.
  956.         if (rowindex != iRow - 1) {
  957.             if (rowindex == iRow - 2) {
  958.                 // next row in sequence.
  959.                 rowindex++;
  960.                 row = (Element)iter.nextElement();
  961.             } else {
  962.                 // caller just did a random jump, so we need to resync
  963.                 // the iterator.
  964.                 iter.reset();
  965.                 row = (Element)iter.nextElement();
  966.                 rowindex = 0;
  967.                 for (int i = 0; i < iRow - 1; i++) {
  968.                     row = (Element)iter.nextElement();
  969.                     rowindex++;
  970.                 }
  971.             }
  972.         }
  973.         return row;
  974.     }
  975.  
  976.     public Object getColumn(Element row, int col)
  977.     {
  978.         Element se = schema.getChildren().getChild(col);
  979.         Name name = Name.create((String)se.getAttribute(nameNAME));  
  980.         if (se.getTagName() == nameCOLUMN) {
  981.             Element child = findChild(row,name);
  982.             if (child != null) {
  983.                 String text = child.getText();
  984.                 return text;
  985.             }
  986.             return null;
  987.         } else {
  988.             // Must be a rowset, so return a rowset provider.
  989.             // First see if we've already created one for this row.
  990.             XMLRowsetProvider value = findChildProvider(row);
  991.             if (value == null)
  992.             {
  993.                 // Have to create a new rowset provider then.
  994.                 value = new XMLRowsetProvider(row,se,factory,this);
  995.                 addChildProvider(value);
  996.             }
  997.             return value;
  998.         }
  999.     }
  1000.  
  1001.     public XMLRowsetProvider findChildProvider(Element row)
  1002.     {
  1003.         if (childProviders != null) {
  1004.             for (Enumeration en = childProviders.elements(); en.hasMoreElements(); )
  1005.             {
  1006.                 XMLRowsetProvider child = (XMLRowsetProvider)en.nextElement();
  1007.                 if (child.root == row)
  1008.                 {
  1009.                     return child;
  1010.                 }
  1011.             }
  1012.         }
  1013.         return null;
  1014.     }
  1015.  
  1016.     void addChildProvider(XMLRowsetProvider child)
  1017.     {
  1018.         if (childProviders == null) 
  1019.             childProviders = new Vector();
  1020.         childProviders.addElement(child);
  1021.     }
  1022.  
  1023.     void removeChildProvider(Element row)
  1024.     {
  1025.         XMLRowsetProvider value = findChildProvider(row);
  1026.         if (value != null) 
  1027.         {
  1028.             childProviders.removeElement(value);
  1029.         }
  1030.     }
  1031.  
  1032.     /**
  1033.      * Recurrsively search given row for first child or grand-child 
  1034.      * node with matching tag name.
  1035.      */
  1036.     public Element findChild(Element row, Name tag)
  1037.     {
  1038.         for (ElementEnumeration en = new ElementEnumeration(row,null,Element.ELEMENT);
  1039.                 en.hasMoreElements(); )
  1040.         {
  1041.             Element child = (Element)en.nextElement();
  1042.             if (child.getType() == Element.ELEMENT && child.getTagName() == tag) {
  1043.                 return child;
  1044.             } else if (child.numElements() > 0) {
  1045.                 child = findChild(child,tag);
  1046.                 if (child != null)
  1047.                     return child;
  1048.             }
  1049.         }
  1050.         return null;
  1051.     }
  1052.  
  1053.     public void setVariant(int iRow,int iColumn, int formatType, Object var) 
  1054.     {
  1055.         getRow(iRow); // update current row.
  1056.         if (row == null)
  1057.             return; // non-existant row.
  1058.         Element se = schema.getChildren().getChild(iColumn-1);
  1059.         if (se.getTagName() == nameCOLUMN) {
  1060.             String attr = (String)se.getAttribute(nameNAME);
  1061.             if (attr != null) 
  1062.             {
  1063.                 Name name = Name.create(attr);
  1064.                 Element child = findChild(row,name);
  1065.                 if (child == null) {
  1066.                     child = factory.createElement(Element.ELEMENT,name);
  1067.                     row.addChild(child,null); // order doesn't actually matter.
  1068.                 }
  1069.                 if (child != null) {
  1070.                     if (child.numElements() == 0) 
  1071.                     {
  1072.                         child.addChild(factory.createElement(Element.PCDATA,null),null);
  1073.                     }
  1074.  
  1075.                     if (child.numElements() == 1 &&
  1076.                         child.getChild(0).getType() == Element.PCDATA)
  1077.                     {
  1078.                         if (listener != null) listener.aboutToChangeCell(iRow,iColumn);
  1079.                         Element pcdata = child.getChild(0);
  1080.                         String text = (String)var;
  1081.                         pcdata.setText(text);
  1082.                         if (listener != null) listener.cellChanged(iRow,iColumn);
  1083.                     }
  1084.                     // otherwise ignore the setVariant !
  1085.                 }
  1086.             }
  1087.         }
  1088.     }
  1089.  
  1090.     public OLEDBSimpleProviderListener getListener()
  1091.     {
  1092.         return listener;
  1093.     }
  1094.  
  1095.     public String getLocale()
  1096.     {
  1097.         // nothing special here. Return the default locale, ie. US
  1098.         return "";
  1099.     }
  1100.  
  1101.     public int isAsync()
  1102.     {
  1103.         return 0;
  1104.     }
  1105.  
  1106.     public void stopTransfer()
  1107.     {
  1108.     }
  1109.     
  1110.     Element root;
  1111.     Element schema;
  1112.     Element row;
  1113.     ElementEnumeration iter;
  1114.     int     rowindex;
  1115.     Name    rowset;
  1116.     XMLRowsetProvider parent;
  1117.     OLEDBSimpleProviderListener listener;
  1118.     ElementFactory factory;
  1119.     Vector  childProviders; 
  1120.  
  1121.     static Name nameCOLUMN = Name.create("COLUMN");
  1122.     static Name nameROWSET = Name.create("ROWSET");
  1123.     static Name nameNAME = Name.create("NAME");
  1124.     static Name nameATTRIBUTE = Name.create("ATTRIBUTE");
  1125.     static Name nameVALUE = Name.create("VALUE");
  1126. }
  1127.  
  1128.  
  1129. //---------------------------------------------------------------------
  1130. class XMLParserThread extends Thread
  1131. {
  1132.     URL url;
  1133.     JSObject win;
  1134.     String callback;
  1135.  
  1136.     XMLParserThread(URL url, JSObject win, String callback)
  1137.     {
  1138.         this.url = url;
  1139.         this.win = win;
  1140.         this.callback = callback;
  1141.     }
  1142.  
  1143.     public void run()
  1144.     {
  1145.         Object args[] =  new Object[2];
  1146.         try
  1147.         {
  1148.             Document document = new Document();
  1149.             document.load(url);
  1150.             args[0] = "ok";
  1151.             args[1] = document;
  1152.             win.call(callback, args);
  1153.         }
  1154.         catch (ParseException e)
  1155.         {
  1156.             args[0] = e.toString();
  1157.             args[1] = null;
  1158.             win.call(callback, args);
  1159.         }
  1160.     }
  1161. }
  1162.