Second Life of a Hungarian SharePoint Geek

February 8, 2010

Exporting TermSet and Group – under construction, and other tales about taxonomy export / import

Filed under: Reflection, SP 2010, Taxonomies — Tags: , , — Peter Holpar @ 19:19

It seems that exporting TermSet and Group hierarchies in the Beta 2 bits is not ready to use. The source of the Export methods for these classes shows that after a few lines of tracing it simply throws a NotImplementedException.

At first sight it’s even stranger that there is no Import method corresponding to Export – at least you cannot find that neither in the TermSet class nor in the related classes, like TermStore or TaxonomySession. So what user interface or class should we use to import taxonomies manually or from code?

You might notice that there is a Sample Import section on the Term Store Management Tool page. You should click on the name of the term store on the left side tree view to make this option visible:

image

There are some references on the web about the CSV file you should use to import term sets, but until now I have not found information about how one should use that file to import the data, and unfortunately I have not remembered this detail from SPC 2009. The TechNet article Managed metadata input file format says: “For instructions about how to import metadata, see Office.com”, but where exactly, please?!

Let’s try from the coding side! The good news is that importing has its dedicated class called ImportManager, the bad news is that its constructors are not public. But such minor issues cannot stop us from using the services of this class.

The folowing code snippet shows you how to do that using Reflection. Do not forget to add the necessary using directives to your code referencing the System.Reflection and System.IO namespaces. The code assumes you’ve saved the sample CSV file as C:\work\ImportTermSet.csv on the server.

  1. Type importManagerType = typeof(ImportManager);
  2. Type[] parameterTypes = new Type[1] { typeof(TermStore) };
  3. ConstructorInfo constInfo = importManagerType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
  4.     null, CallingConventions.ExplicitThis, parameterTypes, null);
  5.  
  6. if (constInfo != null)
  7. {
  8.     TaxonomySession session = new TaxonomySession(site);
  9.     TermStore termStore = session.TermStores["MetadataTermStoreName"];
  10.     Object[] callParams = new Object[1] { termStore };
  11.     ImportManager importManager = (ImportManager)constInfo.Invoke(callParams);
  12.     StreamReader reader = new StreamReader(@"C:\work\ImportTermSet.csv");
  13.     Group group = termStore.Groups["NameOfTheGroup"];
  14.     bool allTermsAdded;
  15.     String errorMessage;
  16.     importManager.ImportTermSet(group, reader, out allTermsAdded, out errorMessage);
  17.     Console.WriteLine("All terms added: {0}", allTermsAdded);
  18.     Console.WriteLine("Error message: {0}", errorMessage);
  19. }
  20. else
  21. {
  22.     Console.WriteLine("Error message: No constructor found");
  23. }

There is a default batch size of 1000 – I assume it means 1000 lines of items are read up before processing, than the next 1000 lines follow, etc. – , but you can change that before starting the import process using the BatchSize property.

Analyzing the ImportManager class using Reflector leads us to the user interface where you can do the import as well. The code behind of the _Layouts/inputcsvfile.aspx instantiates this class, see it on the following figure:

image

Following the call chain of JavaScript methods shows that the modal dialog is displayed by the _Layouts/termstoremanager.aspx, where I searched this a lot previously. You should first click on the group name to highlight it, then click on the small black arrow right to the group name to display the menu.

image

If you consider it is hacking to use Reflection or would like to have the source code to create an export / import solution that fits better to your needs than the original one then you can create your own tool to complete the export and import processes.

I like custom command like tools so I created my own utility to export and import term sets. I’ve chosen XML file format, that makes it possible to create term sets deeper than the 7 levels limitation of the CSV file , furthermore you can include multiple term sets in a single file.

I’ve attached the full code of the utility (reference the Microsoft.SharePoint.Taxonomy.dll assembly in your project in addition to the standard Microsoft.SharePoint.dll!), but be prepared that it is not over-tested, so you should use it at your own risk.

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Xml;
  6. using Microsoft.SharePoint;
  7. using Microsoft.SharePoint.Taxonomy;
  8.  
  9. namespace ImportTerms
  10. {
  11.     class Program
  12.     {
  13.         //0: operation
  14.         //1: site
  15.         //2: xml file
  16.         //3: term store name
  17.         //4: group name
  18.         //5…: term set(s) name (export only!)
  19.         static void Main(string[] args)
  20.         {
  21.  
  22.             try
  23.             {
  24.  
  25.                 if (args.Length >= 5)
  26.                 {
  27.                     String operation = args[0].ToLower();
  28.                     String siteUrl = args[1];
  29.                     String dataFilePath = args[2];
  30.                     String termStoreName = args[3];
  31.                     String groupName = args[4];
  32.                     if (operation == "export")
  33.                     {
  34.                         if (args.Length > 5)
  35.                         {
  36.                             int termSetCount = args.Length – 5;
  37.                             String[] termSets = new String[termSetCount];
  38.                             Array.Copy(args, 5, termSets, 0, termSetCount);
  39.                             Export(siteUrl, dataFilePath, termStoreName, groupName, termSets);
  40.                         }
  41.                         else
  42.                         {
  43.                             ShowUsage();
  44.                         }
  45.                     }
  46.                     else if (operation == "import")
  47.                     {
  48.                         if (args.Length == 5)
  49.                         {
  50.                             Import(siteUrl, dataFilePath, termStoreName, groupName);
  51.                         }
  52.                         else
  53.                         {
  54.                             ShowUsage();
  55.                         }
  56.                     }
  57.  
  58.                 }
  59.                 else
  60.                 {
  61.                     ShowUsage();
  62.                 }
  63.             }
  64.  
  65.             catch (Exception ex)
  66.             {
  67.                 Console.WriteLine(ex.ToString());
  68.             }
  69.         }
  70.  
  71.         private static void Export(String siteUrl, String dataFilePath, String termStoreName, String groupName, String[] termSets)
  72.         {
  73.             Console.WriteLine("Connecting to server…");
  74.             using (SPSite site = new SPSite(siteUrl))
  75.             {
  76.                 XmlTextWriter writer = new XmlTextWriter(dataFilePath, Encoding.UTF8);
  77.                 writer.Formatting = Formatting.Indented;
  78.                 writer.WriteStartElement("TermSets");
  79.  
  80.                 TaxonomySession session = new TaxonomySession(site);
  81.                 TermStore termStore = session.TermStores[termStoreName];
  82.                 Group group = termStore.Groups[groupName];
  83.                 foreach (String termSetName in termSets)
  84.                 {
  85.                     Console.WriteLine("Exporting term set: {0}", termSetName);
  86.  
  87.                     writer.WriteStartElement("TermSet");
  88.  
  89.                     TermSet termSet = group.TermSets[termSetName];
  90.                     writer.WriteAttributeString("name", termSetName);
  91.                     if (!String.IsNullOrEmpty(termSet.Description))
  92.                     {
  93.                         writer.WriteAttributeString("description", termSet.Description);
  94.                     }
  95.                     writer.WriteAttributeString("isForTagging", termSet.IsAvailableForTagging.ToString());
  96.  
  97.                     foreach (Term term in termSet.Terms)
  98.                     {
  99.                         AddTerm(writer, term);
  100.                     }
  101.  
  102.                     writer.WriteEndElement(); // TermSet
  103.  
  104.                 }
  105.                 writer.WriteEndElement(); // Root
  106.                 writer.Flush();
  107.                 writer.Close();
  108.             }
  109.         }
  110.  
  111.         private static void AddTerm(XmlTextWriter writer, Term term)
  112.         {
  113.             writer.WriteStartElement("Term");
  114.  
  115.             if (!String.IsNullOrEmpty(term.GetDescription()))
  116.             {
  117.                 writer.WriteAttributeString("description", term.GetDescription());
  118.             }
  119.             writer.WriteAttributeString("name", term.Name);
  120.             writer.WriteAttributeString("isForTagging", term.IsAvailableForTagging.ToString());
  121.  
  122.             foreach (Term subTerm in term.Terms)
  123.             {
  124.                 AddTerm(writer, subTerm);
  125.             }
  126.  
  127.             writer.WriteEndElement(); // Term
  128.         }
  129.  
  130.         private static void Import(String siteUrl, String dataFilePath, String termStoreName, String groupName)
  131.         {
  132.  
  133.             Console.WriteLine("Connecting to server…");
  134.             using (SPSite site = new SPSite(siteUrl))
  135.             {
  136.                 TaxonomySession session = new TaxonomySession(site);
  137.  
  138.                 XmlDocument dataDoc = new XmlDocument();
  139.                 Console.WriteLine("Loading…");
  140.                 dataDoc.Load(dataFilePath);
  141.  
  142.                 XmlNode rootNode = dataDoc.SelectSingleNode("TermSets");
  143.  
  144.                 TermStore termStore = session.TermStores[termStoreName];
  145.                 Group group = termStore.Groups[groupName];
  146.  
  147.                 foreach (XmlNode termSetNode in rootNode.SelectNodes("TermSet"))
  148.                 {
  149.                     String termSetName = GetStringAttribute(termSetNode, "name", null, true);
  150.                     Console.WriteLine("Importing term set: {0}", termSetName);
  151.                     String description = GetStringAttribute(termSetNode, "description", null, false);
  152.                     bool isForTagging = GetBoolAttribute(termSetNode, "isForTagging", true, false);
  153.                     TermSet termSet = group.CreateTermSet(termSetName);
  154.                     if (!String.IsNullOrEmpty(description))
  155.                     {
  156.                         termSet.Description = description;
  157.                     }
  158.                     termSet.IsAvailableForTagging = isForTagging;
  159.                     AddNodes(termSet, termSetNode);
  160.                 }
  161.  
  162.                 Console.WriteLine("Commiting…");
  163.                 termStore.CommitAll();
  164.             }
  165.             Console.WriteLine("Done.");
  166.  
  167.         }
  168.  
  169.         private static void AddNodes(TermSetItem termSetItem, XmlNode termSetItemNode)
  170.         {
  171.             foreach (XmlNode termNode in termSetItemNode.SelectNodes("Term"))
  172.             {
  173.                 String termName = GetStringAttribute(termNode, "name", null, true);
  174.                 String description = GetStringAttribute(termNode, "description", null, false);
  175.                 int lcid = (int)GetIntAttribute(termNode, "lcid", termSetItem.TermStore.DefaultLanguage, false);
  176.                 bool isForTagging = GetBoolAttribute(termNode, "isForTagging", true, false);
  177.                 Term term = termSetItem.CreateTerm(termName, lcid);
  178.                 if (!String.IsNullOrEmpty(description))
  179.                 {
  180.                     term.SetDescription(description, lcid);
  181.                 }
  182.                 term.IsAvailableForTagging = isForTagging;
  183.                 AddNodes(term, termNode);
  184.             }
  185.         }
  186.  
  187.         private static string GetStringAttribute(XmlNode node, string attrName, string defaultValue, bool required)
  188.         {
  189.             string value = defaultValue;
  190.             if (node.Attributes[attrName] == null)
  191.             {
  192.                 if (required)
  193.                 {
  194.                     throw new ApplicationException(String.Format("Missing attribite {0}", attrName));
  195.                 }
  196.             }
  197.             else
  198.             {
  199.                 value = node.Attributes[attrName].Value;
  200.             }
  201.             return value;
  202.         }
  203.  
  204.         private static bool GetBoolAttribute(XmlNode node, string attrName, bool defaultValue, bool required)
  205.         {
  206.             bool value = defaultValue;
  207.             if (node.Attributes[attrName] == null)
  208.             {
  209.                 if (required)
  210.                 {
  211.                     throw new ApplicationException(String.Format("Missing attribite {0}", attrName));
  212.                 }
  213.             }
  214.             else
  215.             {
  216.                 value = bool.Parse(node.Attributes[attrName].Value);
  217.             }
  218.             return value;
  219.         }
  220.  
  221.         private static int? GetIntAttribute(XmlNode node, string attrName, int? defaultValue, bool required)
  222.         {
  223.             int? value = defaultValue;
  224.             if (node.Attributes[attrName] == null)
  225.             {
  226.                 if (required)
  227.                 {
  228.                     throw new ApplicationException(String.Format("Missing attribite {0}", attrName));
  229.                 }
  230.             }
  231.             else
  232.             {
  233.                 value = int.Parse(node.Attributes[attrName].Value);
  234.             }
  235.             return value;
  236.         }
  237.  
  238.  
  239.         private static void ShowUsage()
  240.         {
  241.             String appName = GetAppName();
  242.             Console.WriteLine("Usage: \r\n   {0} export [SieUrl] [ExportFileName] \r\n  [TermStoreName] [GroupName] [TermSetName1] [TermSetName2] … [TermSetNameN]", appName);
  243.             Console.WriteLine("or\r\n");
  244.             Console.WriteLine("{0} import [SieUrl] [ImportFileName] \r\n  [TermStoreName] [GroupName]", appName);
  245.             Console.WriteLine(@"Example: \r\n   {0} export http://sp2010 c:\temp\export.xml \r\n  Metadata MyGroup Products Languages Countries", appName);
  246.             Console.WriteLine(@"or\r\n   {0} import http://sp2010 c:\temp\import.xml \r\n  Metadata MyGroup", appName);
  247.  
  248.         }
  249.  
  250.         private static String GetAppName()
  251.         {
  252.             String path = Environment.GetCommandLineArgs()[0];
  253.             int startName = path.LastIndexOf(@"\") + 1;
  254.             return path.Substring(startName);
  255.         }
  256.     
  257.     }
  258.  
  259. }

And here is a simple import file for your convenience as well:

  1. <TermSets>
  2.   <TermSet name="Language">
  3.     <Term name="English" />
  4.     <Term name="French" />
  5.     <Term name="German" />
  6.   </TermSet>
  7. </TermSets>

Advertisements

3 Comments »

  1. […] my tool (see a former version here) I load a lot of taxonomy terms into multiple term sets. If I try to create a term that already […]

    Pingback by TermStoreOperationException provides no help when you need to localize the source of the error « Second Life of a Hungarian SharePoint Geek — April 22, 2010 @ 14:58

  2. Hi Péter!
    To export Keywords from a farm, this code works very well for me.
    But when I try to import the keywords to a different farm, it gives the following error:
    “Creating a term set in system Group is disallowed.”
    Is there a way to import the terms into System –> Keywords without “creating” it doing so?
    Thanks for your time!

    Comment by Hans Erik Storeide — June 1, 2010 @ 23:33

  3. Hi,
    I used your code to find if a SPContentType exist in SPContentTypeCollection and it work great.

    Is there a way to check id SPView exist in SPViewCollection?

    Thanks in advance.

    Giorgio

    Comment by Gio — March 26, 2013 @ 11:07


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: