Web Q&A: Threading in MSXML, Sorting XML, Order...

We were unable to locate this content in de-de.

Here is the same content in en-us.

From the February 2002 issue of MSDN Magazine
MSDN Magazine
Threading in MSXML, Sorting XML, Order-by, Changing Mouse Pointer, and More
Edited by Nancy Michell
Download the code for this article: Web0202.exe (36KB)
Q The MSXML 4.0 SDK documents say that Msxml2.DOMDocument.4.0 is apartment threaded and Msxml2.FreeThreadedDOMDocument.4.0 is freethreaded. But after I installed the 4.0 package and examined the register, the Msxml2.DOMDocument.4.0 is also marked as "Both" threading models. Can you tell me if the documentation is wrong?

A Sort of. DOMDocument is not really as limited as "apartment threaded" implies. It will work without problems on any thread, as long as it is never used simultaneously on multiple threads. This includes calling AddRef/Release. The FreeThreadDOMDocument adds API-level locking to support safe multithreaded access, as well as thread-safe AddRef/Release. The client of DOMDocument.4.0 is responsible for making sure that simultaneous use by multiple threads won't happen. Delegating the responsibility for maintaining thread safety to COM/OLE slows large document DOM tree walks by a factor of roughly 1000.

Q I was querying SQL with ADO persisted as XML, and I got the following schema for a field:
<s:AttributeType name='CaseIdleDays' rs:number='5' 
 rs:nullable='true'>
  <s:datatype dt:type='float' dt:maxLength='8' rs:precision='15'  
              rs:fixedlength='true'/>
</s:AttributeType>
However, when I used XSL for transformation (xmlns:xsl="http://www.w3.org/TR/WD-xsl"), the order-by attribute in a for-each element sorted the field like strings, instead of like numbers. Am I missing something?

A Try using the following code, replacing @CaseldleDays with whatever field you are trying to sort on:
<xsl:for-each select="whatever" order-by=number(@CaseldleDays)> 
Though I'd recommend moving to the XSLT implementation in MSXML 3.0, in which case you can use <xsl:sort data-type"number">, I would also recommend using the built-in XML features of SQL Server™ 2000 (use SQLXML Web Release 2, if you're still using SQL Server 7.0). SQLXML provides faster XML serialization than ADO recordsets and allows you to easily sort your data directly in the database without requiring XSL post-processing.
      With SQLXML, you can do either client-side or server-side transformations, or both. Sorting data on the server allows you to stream the XML results very efficiently to the client. On the other hand, if you know that your server is already too busy completing other tasks and you have to offload computation to the client, then sorting on the client is the answer.

Q I am trying to use an xsl:for-each statement to process XML nodes in reverse document order. I've tried various order-by clauses, but this seems to be limited to ordering by the value of the node while I want to order by the childnumber of the node.
      So, for this XML:
<foo>
    <a>3</a>
    <a>1</a>
    <a>4</a>
</foo>
<xsl:for-each select="foo/a"> processes the nodes like so:
3
1
4
But, I want the nodes to be processed like this:
4
1
3
A One solution is to use the number function in the old namespace (http://www.w3.org/TR/WD-xsl), like so:
order-by="- number(a)"
But please note this solution works only with the old namespace:
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
  <xsl:for-each select="foo/a" order-by="- number(a)">
    <a><xsl:value-of select="."/></a>
  </xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The number function has different meanings in the old namespace and the new one. Using the new namespace you can write
<xsl:sort select="-position()" data-type="number"/>
inside your <xsl:for-each> element. And by the way, if no data type is specified, the expression will default to a data-type of "text."

Q How do I change the mouse pointer shape in VBScript? After clicking the button, I want to change the pointer to an hourglass (busy). The problem is that I have an ASP.NET button (which executes on the server side) and I want to change the mouse pointer on the onclick event of that button.

A Here's an example (not ASP.NET-specific) that may help (see Figure 1). The code to change the cursor is encapsulated in the function SetWait. You can call the function using the onclick method of the button. This simple example shows how to change the button text to "Cancel" and not have the cursor be an hourglass when over the button, but you can adapt it to your specific needs.

Q Is there any way to obtain the absolute XPath of a node within a document ?
      For example, consider the following snippet :
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001

 /XMLSchema-instance">
    <soap:Body>
        <Result xmlns="urn:acme-org:results">
            <Job>
                <contacts>
                    <contact>J Doe</contact>
                    <contact>J Q Public</contact>
                </contacts>
</soap:Envelope>
I'd like to know the absolute path of the second "contact" node:
/soap:Envelope/soap:Body[1]/Result/Job/contacts/contact[2]
A You may want to generate absolute XPath to a particular element node. This can be done easily with the following XSLT fragment:
<xsl:for-each select="ancestor- or-self::*">/<xsl:value-of 
   select="name()"/></xsl:for-each>
      The resulting XPath expression will contain node names like /A/B/C, but what if element B has more than one C element as its child? You can add a position predicate to the expression:
<xsl:for-each select="ancestor-or-self::*">/<xsl:value-of 
   select="name()"/>[<xsl:number />]</xsl:for-each>
Now the expression can look like /A[1]/B[1]/C[2], for example.
      You can go further and generate position predicates only if it is needed (there is more than one node with this name):
<xsl:for-each select="ancestor-or-self::*">
    <xsl:text>/</xsl:text><xsl:value-of select="name()"/>
    <xsl:if test="count(parent::node()/*[local-name() = local-
      name(current()) and namespace-uri() = 
      namespace-uri(current())]) != 1">[<xsl:number />]</xsl:if>
</xsl:for-each>
      It's also possible to extend this code to generate templates for attributes and text nodes as well. Templates that do the trick are included in this month's download available at the link at the top of this article.
      Please note that there are some limitations of this technology. If the element has a non-null default namespace you will generate it with an empty prefix in the XPath expression and it will never be matched. For example, <A><B xmlns="Foo" /><A/> generates A/B. That is not correct.

Q I am developing a Web-based application that uses the MSXML Parser 3.0 SP2 on the server and the client (with JScript® client code). I need to know exactly which version of MSXML is installed on the client so it can ask for an upgrade. Is there a way to identify the version on the client with JScript code or a Visual Basic®-based app? For security reasons, the client is not able to read the registry.

A This can be done on the client with JScript by using nested try/catch blocks. Start out with a null object reference. Then try to load the most recent version of MSXML using the version-dependent PROGID. If that fails, start another try/catch block and try to load the next most recent version of MSXML. Keep repeating this process until you finally get back a valid object, at which point you should know exactly which version of MSXML is installed on the client machine in question.
      A simple function to test whether MSXML 3.0 is installed is shown in Figure 2. You could extend this by adding additional tests inside the nested catch blocks.

Figure 3 XML Version Detector
Figure 3 XML Version Detector

      Figure 3 shows an extended XML version detector in action. Figure 4 lists the code.

Q I have the following XML:
<testmodule>
  <testcases filter="somefilter">
    <testcase>
      <variations>
        <variation filter="bvt">
          •••
        </variation>
      </variations>
    </testcase>
  </testcases>
</testmodule>
 
      The filter attribute, which currently sits on the variation node, can also be present on any other level (testmodule, testcase, variations, and so on). I am looking for an XPath query which, when executed at the lowermost variation node, would give me the nearest/lowest-level filter attribute value in the tree. In this case I should get "bvt." In the following case I should get "labrun," not "extended."
<testmodule filter="extended">
  <testcases>
    <testcase filter="labrun">
      <variations>
        <variation>
          •••
        </variation>
      </variations>
    </testcase>
  </testcases>
</testmodule>
A This query returns the value of the filter attribute of the current node or one of its parents, whichever is closer.
<xsl:value-of select="ancestor-or-self::*[@filter][1]/@filter"/>
Q To be certain that our users can find information on our site, we want to have two navigation methods leading to the same places. What's the best way to accomplish this?

A It's best not to do this at all. Your goal should be speed and accuracy, and if you offer more than one choice, you're going to leave users wondering what subtle differences there may be between the choices. It actually takes several attempts for them to learn that it's just a different link to the same place, which is generally very frustrating for users.

Got a question? Send questions and comments to webqa@microsoft.com.
Thanks to the following Microsoft developers for their technical expertise: Tiziano Bergonzi, Derek Denny-Brown, Daniel Cheung, Arpan Desai, Sergey Dubinets, Pranav Kandula, Anton Lapounov, Dean Pachosa, Ken Stanfield, Karthik Veeraswamy, Suveen Kumar Reddy Vuppala, Barry Webberley, Guang-an Wu, Feng Yue, and Meiji Yugawa.

Page view tracker