Friday, February 5, 2010

C#.net - Find and Replace in large files

Problem-Scenario: When we are working on large files (e.g. > 1GB) and you have to do simple operation like Replace a string with another string.
regex.replace or string.replace or using xmldom object or LINQ doesn't work! And there are no free tools in market which does it without throwing up in middle.
  
Solution:
Well the solution is as simple as it can get. Just use StreamReader & StreamWriter. These don't load file in memory but streams through your text or xml file byte by byte

Example: (Change the required parameter values for changing a site's site definition)

            /// Replaces text in a file.
            ///
Path of the text file.
            ///Text to search for.
            ///Text to replace the search text.
            

public void ReplaceInFile(string SourcefilePath, string DestfilePath)
            {
               string data;
                if( !(SourcefilePath.Contains(".xml")) )
                {
                    Console.WriteLine("Please specify Manifest.xml path.. Filename is missing");
                    return;
                }
               if(File.Exists(SourcefilePath) == false)
               {
                   Console.WriteLine("File doesn't exist at the specified path\n");
                   return;
               }
              

            StreamReader streamReader = new StreamReader(SourcefilePath);
            StreamWriter streamWriter = new StreamWriter(DestfilePath);

            while (streamReader.Peek() >= 0)
            {

                data = streamReader.ReadLine();

                //**********************************
                // Strings for changing the Configuration IDs
                string OldConfig1 = @"Configuration=""0""";
                string NewConfig = @"Configuration=""2""";

                //-1. Webtemplate="InsideCustompublishingWorkflow - Config as -1 change it to 2
                string searchtext23 = @"WebTemplate=""InsideCustompublishingWorkflow""";

                if (data.Contains(searchtext23) == true)
                {
                    //change the configuration
                    data = data.Replace(OldConfig1, NewConfig);
                 }

           
                //CHANGE THE SITE TEMPLATE NAME
                // 1. WebTemplate="INSIDECustomPUBLISHING" - Aold
                string searchtext1 = @"WebTemplate=""INSIDECustomPUBLISHING""";
                string replacetext1 = @"WebTemplate=""INSIDECustomPUBLISHINGnew""";

                if (data.Contains(searchtext1) == true)
                {
                    //change the configuration
                    data = data.Replace(OldConfig1,NewConfig);
                    data = data.Replace(searchtext1, replacetext1);
                   
                }
               

                //CHANGE THE SITE TEMPLATE SETUPPATH
                //
INSIDECustomPUBLISHING - Aold //SetupPath="SiteTemplates\INSIDECustomPUBLISHING"
                string searchtext4 = @"SetupPath=""SiteTemplates\
INSIDECustomPUBLISHING\";
                string replacetext4 = @"SetupPath=""SiteTemplates\
INSIDECustomPUBLISHINGnew\";
                data = data.Replace(searchtext4, replacetext4);


                //Write the data on .xmlnew file
                streamWriter.WriteLine (data);

            }

            streamReader.Close();
            streamWriter.Close();
}

 

How to change an existing site's site definition?

Problem-Scenario:
I faced this problem in one of my projects. There were many custom site definitions beings used in the organization & few were all messed up; so they wanted me to change the site definition of all the existing sites to just one custom site definition.

Solution:
Now there are two supported ways of changing a site's site definition. Yeah! you heard it right. It is supported by Microsoft.

First way is using the sharepoint deployment API. You can refer to Stefan Gobner's blog on this:
http://blogs.technet.com/stefan_gossner/archive/2007/08/30/deep-dive-into-the-sharepoint-content-deployment-and-migration-api-part-4.aspx
I personally found it difficult, so i analyzed the manifest.xml & did lot of testing to find another simpler solution which doesn't use SharePoint API but does the same thing.

Note: you cannot save site as template (.Stp) because it has limitation of maximum 10MB size which makes the approach useless

The second way is to manipulate the manifest.xml directly
Steps:

1) Find the template name, setuppath, site definition ID, Configuration ID of all the site definitions which needs to be changed as well as of the new site definition. 
For that go to
C:\\Program Files\\Common Files\\Microsoft Shared\\web server extensions\\12\\TEMPLATE\\1033\\XML  open the webtemp.xml of your site definition. after couple of lines, you will see:
"CustomsiteDefitionName" is template name and "50" is template ID or sitedefinition ID
What is the configuration ID for the configuration we want to use?
Our sites are using "Custom team Site", so our configuration id is "0"
In the file, you will also see SetupPath=""SiteTemplates\PUBLISHING\"; for publishing site for example.
Note down these for all site definitions in question.
2) Export the site whose site definition you want to change
stsadm -o export  -url   -filename  [-includeusersecurity]  [-versions] <1-4> -nofilecompression   [-quiet]
This stsadm command will export the site, including it’s subsites, in non compressed fashion. The result will be lot of .dat or data files, few xml – important ones -> Manifest.Xml & Requirements.xml
3) Open Manifest.xml &Requirements.xml & change TemplateName, TemplateID,ConfigurationID & Setuppath of the current site definition to the new sitedefinition's one
That's it & you are almost done.

4) Import this site back to its place. You can delete or overwrite the previous version..upto you. I would suggest taking backup first.
5) Njoy! your site's site definition has been changed.
Note: If your manifest.xml grows bigger (like more than 1GB), any DOM manipulation won't work on it. All DOM ways of opening big files fail since it loads everything in memory. Read my blog on how to handle it...

Thursday, January 28, 2010

Get GUID of SPListItem

Category: Working with SPListItem

Scenario: I was trying to check a column value in splistitem in an Asynchronous event handler.
Since it is asynchronous, the values had already changed & the before state is unaccessible through properties object.

Problem: How to get GUID of a SPListItem so that you can access list item values before the event handler updates it

Solution: use SPListItem.UniqueId

Example:

Guid SiteGuid = (Guid)properties.SiteId;
string weburl = (string)properties.WebUrl;
using (SPSite tempsite = new SPSite(SiteGuid))
{
using (SPWeb tempweb = tempsite.OpenWeb(()))
{
SPList templist = tempweb.Lists[];
Guid templistitemID = (Guid)properties.ListItem.UniqueId;  //get GUID of splistitem
SPListItem templistitem = templist.Items[templistitemID]; //for accessing splistitem values
}
}

Wednesday, January 6, 2010

Two Level Grouping in XSLT 1.0 in SharePoint Content Query Webpart


Category: XSLT1.0 & SharePoint's Content Query WebPart
Problem/ Scenario: 
We have to display data in a Content Query WebPart from a SharePoint list & apply two level grouping to it. Below is an example of two level grouping.
We have three columns in the list namely FoundationName, CauseName & FundName. We need to group the data first by FoundationName & then by Causename. Below is an example of how the data should look like in the end.
Foundation1
            Cause1
                        Fund1
                        Fund2
                        Fund3
            Cause2
                        Fund4
Foundation2
            Cause1
                        Fund5
            Cause3
                        Fund6
Solution:
Step 1:
In order to understand how SharePoint renders the Content Query Web Part, let's look at the Raw Xml
Refer to: http://www.sharepointblogs.com/radi/archive/2009/03/17/content-query-web-part-getting-a-full-dump-of-the-raw-xml.aspx
Below is the raw Xml I got when i connected to my list:
<dsQueryResponse>
    <Rows>
      <Row ListId="40EB7A9E-6F40-479B-B510-D321E60A47E7" WebId="72FAC64E-A65A-4F66-81CA-0240777D7FB2" ID="2" _x007B_fa564e0f_="Brain" FileRef="Site1/Lists/CustomList/2_.000" _x002D_9fedbadb6ce1_x007D_="2" Modified="2009-06-22 16:58:20" Author="System Account" Editor="System Account" Created="2009-06-17 18:01:40" PublishingRollupImage="" _Level="1" Comments="" Title="Brain" Description="<div>this is leader text for Brain</div>" NewFeatured="Featured" SiteUrl="http://Server/site1" ContextScriptID="ctxg_588a882e_6730_4693_b3f0_2bbf668aa8021" ItemIndex="2" ItemContentType="" ItemContentTypeID="" ItemApplicationName="" ItemOpenMethod="" ItemCheckedOut="0" ItemCheckedOutUserID="" ItemIconUrl="blank.GIF" ItemExtension="" DocumentFolder="Site1/Lists/CustomList" ItemUrl="/Site1/Lists/CustomList/2_.000" DocType="" CopySource="" ModerationStatus="0" DocEditCommand="" LinkUrl="http://Server/site1/Lists/CustomList /2_.000" PubDate="Mon, 22 Jun 2009 16:58:20 GMT" ImageUrl="" ImageUrlAltText="" Style="CustomStyle1 " GroupStyle="DefaultHeader" __begincolumn="True" __begingroup="False" />
  </dsQueryResponse>
This will give you an idea on how SharePoint renders XML of Content Query Web part
Step 2:
I am assuming here that you know about ContentQuerymain.xsl, Header.xsl & ItemStyles.xsl. Refer to http://msdn.microsoft.com/en-us/library/bb447557.aspx
Since we have to group our data, we will be coding in ContentQueryMain.xsl. (Note: It is a good practice to create your own custom ContentQueryMain file & make your CQWP reference to this file.)
In ContentQueryMain.xsl, replace
<xsl:template match="/">
    <xsl:call-template name="OuterTemplate" />
  </xsl:template>
with
<xsl:template match="/">
    <xsl:call-template name="OurCustomTemplate" />
  </xsl:template>
Step 3:
We will be using Keys for creating two level grouping. Two keys in our case:
  <xsl:key name="foundationsKey" match="Row" use="@AssociatedFoundation" />
  <xsl:key name="associatedCause" match="Row" use="@AssociatedCause" />
Put these keys just before the code mentioned in Step 2:  in ContentQueryMain.xsl
Here @AssociatedFoundation & @AssociatedCause are internal column names which will be used for grouping
Step 4:
Creating the custom template:
  <xsl:template name=" OurCustomTemplate ">
<xsl:for-each select="/dsQueryResponse/Rows/Row[generate-id(.)=generate-id(key('foundationsKey', @AssociatedFoundation))]">
/*Going upto the <Row> node in the xml & getting Foundation Name*/
<div>
<xsl:value-of select="@AssociatedFoundation" />
</div>
/*Display the Foundation name*/
        <xsl:variable name="thisFoundation" select="@AssociatedFoundation"/>
/*Storing Foundation name in a variable 'thisFoundation'  so that we can use it in our inner loop*/
        <xsl:for-each select="../Row[generate-id() = generate-id(key('associatedCause', @AssociatedCause)[@AssociatedFoundation = $thisFoundation][1])]">
/*For @AssociatedFoundation = the current foundation name, get the associated causes & store in @AssociatedCause */
          <xsl:sort select="@AssociatedCause" /> /*Sort based on AssociatedCause*/
                                                <xsl:for-each select="key('associatedCause', @AssociatedCause)[@AssociatedFoundation = $thisFoundation]">
                                            <xsl:if test="position() = 1">
                                                                                    <xsl:value-of select="@AssociatedCause" />
/*Display the cause when position()=1 else it will display CauseName for every Fund Name*/                       
                    </xsl:if>
                                                        <xsl:value-of select="@FundName" />
/*For the particular foundation name & cause name, display the corresponding Fund Name*/
                                                                                                                    
                </xsl:for-each>
                                    </xsl:for-each>
            </xsl:for-each>
  </xsl:template>


Final Notes: Similar way multi level grouping can be done