0

I am trying to generate an XML file that list XML files that are in a specified folder using xsl:

XML file :

<xml>
   <folder>FolderPath-to-List</folder>
</xml>

Expected result:

<mergeData newRoot="newRoot">
     <fileList>
           <fileItem>path-to-file/file1.xml</fileItem>
           <fileItem>path-to-file/file2.xml</fileItem>
           <fileItem>path-to-file/file3.xml</fileItem>
           <fileItem>path-to-file/file4.xml</fileItem>
     </fileList>
</mergeData>

So far I am able to collect Files list using XSL and embedded script/ JScipt function as follow in the current folder:

<xsl:stylesheet version="1.0"  
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="http://tempuri.org/msxsl"
> 

<msxsl:script language="JScript" implements-prefix="user">
<![CDATA[
      var fso = new ActiveXObject("Scripting.FileSystemObject");

      function   ShowFolderFileList(folderspec)   
      {   
         var   f,   f1,   fc,   s;   

         f   =   fso.GetFolder(folderspec);   
         fc   =   new   Enumerator(f.files);

         s   =   '<fileItem>';   
         for   (;   !fc.atEnd();   fc.moveNext())   
         {   
              s   +=   fc.item(); 

            s   +=   '<fileItem>\n<fileItem>';
         }  
      return(s);   
      }   

  ]]>
  </msxsl:script>

  <xsl:template match="/">
      <mergeData newRoot="Activity">
      <fileList>
    <xsl:value-of select="user:ShowFolderFileList('.')"/>
      </fileList>
      </mergeData>
   </xsl:template>
   </xsl:stylesheet>

But the result is that in place of getting <fileItem> and </fileItem>, I have :

&lt;fileItem&gt;path-to-xml\file.xml&lt;fileItem&gt;

How can I get <fileItem>path-to-xml\file.xml</fileItem>?
How can I get the "FolderPath-to-List" from my XML to be used when calling user:ShowFolderFileList() in place of '.' so far to get it running.

1 Answers1

0

First thing to note is that is that currently you are doing this

<xsl:value-of select="user:ShowFolderFileList('.')"/>

When really you ought to be doing this to use your file path in your XML

<xsl:value-of select="user:ShowFolderFileList(string(xml/folder/text()))" /> 

Note the use of "string()" here, because "text()" actually returns a text node, a not a datatype of string.

Secondly, when you use javascript functions in XSLT in this way, I believe they can only return the simple data types of string and number. Your function is returning a string, not actual XML, when you use xsl:value-of on a string, any reserved symbols will be escaped.

Now, you can be a bit naughty and do this

<xsl:value-of select="user:ShowFolderFileList(string(xml/folder/text()))" 
              disable-output-escaping="yes" />

But this is not necessarily considered good practise, as disable-output-escaping is not widely supported (although obviously it works in Mircosoft's implementation).

However, the only other way to do this (in XSLT 1.0) that I can think of is to return a list of file names, separated by new lines, and write a recursive template. Try this XSLT as an example:

<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"    
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"  
                xmlns:user="http://tempuri.org/msxsl" 
                exclude-result-prefixes="msxsl user">

   <xsl:output method="xml" indent="yes"/>

   <msxsl:script language="JScript" implements-prefix="user">
      <![CDATA[
      var fso = new ActiveXObject("Scripting.FileSystemObject");

      function   ShowFolderFileList(folderspec)   
      {   
         var   f,   f1,   fc,   s;

         f   =   fso.GetFolder(folderspec);   
         fc   =   new   Enumerator(f.files);

         s="";
         for   (;   !fc.atEnd();   fc.moveNext())   
         {   
              s   +=   fc.item() + "\n";
         }  
      return s;
      }   
      ]]>
   </msxsl:script>

   <xsl:template match="/">
      <mergeData newRoot="Activity">
         <fileList>
            <xsl:call-template name="files">
               <xsl:with-param name="files" select="user:ShowFolderFileList(string(xml/folder/text()))"/>
            </xsl:call-template>
         </fileList>
      </mergeData>
   </xsl:template>

   <xsl:template name="files">
      <xsl:param name="files"/>
      <xsl:if test="normalize-space($files) != ''">
         <file>
            <xsl:value-of select="substring-before($files, '&#13;')"/>
         </file>
         <xsl:if test="contains($files, '&#13;')">
            <xsl:call-template name="files">
               <xsl:with-param name="files" select="substring-after($files, '&#13;')"/>
            </xsl:call-template>
         </xsl:if>
      </xsl:if>
   </xsl:template>
</xsl:stylesheet>

There are a couple of other options though:

  1. Don't use XSLT for this!
  2. Upgrade to XSLT 2.0 (Mircosoft don't support it, but you can get other XSLT processors for .Net)

See this question which basically asks the same thing:

XSLT: How to get file names from a certain directory?

Community
  • 1
  • 1
Tim C
  • 70,053
  • 14
  • 74
  • 93
  • 1
    It is possible to create (`new ActiveXObject('Msxml2.DOMDocument.3.0')`) and return an MSXML DOM document in the extension function so that you could simply use `` in XSLT. They only tricky thing is that there are various MSXML versions (MSXML 3, 4, 6)) and I think you need to make sure the script creates a DOM document of the same MSXML version used to run the XSLT. If you want to run the XSLT inside of IE you would need to pass `system-property('msxsl:version')` as different versions of IE use different MSXML versions. – Martin Honnen Sep 14 '13 at 09:39