1892 строки
70 KiB
C#
1892 строки
70 KiB
C#
//using Microsoft.Deployment.Compression.Cab;
|
|
//using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Xml.Linq;
|
|
using System.Data;
|
|
using Microsoft.SharePoint.Client;
|
|
using System.Security;
|
|
using System.Management.Automation;
|
|
using Microsoft.Online.SharePoint.TenantAdministration;
|
|
|
|
namespace Common
|
|
{
|
|
|
|
public class Operations
|
|
|
|
{
|
|
/// <summary>
|
|
/// SPO Properties
|
|
/// </summary>
|
|
public string DownloadPath { get; set; }
|
|
public bool DownloadForms { get; set; }
|
|
public string summaryFile = @"\WorkflowDiscovery.csv";
|
|
public string logFolder = @"\Logs";
|
|
public string downloadedFormsFolder = @"\DownloadedWorkflows";
|
|
public string analysisFolder = @"\Analysis";
|
|
public string summaryFolder = @"\Summary";
|
|
public string analysisOutputFile = @"\WorkflowComparisonDetails.csv";
|
|
public string compOutputFile = @"\WorkflowComparison.csv";
|
|
public DataTable dt = new DataTable();
|
|
|
|
|
|
public void SaveXamlFile(string xamlContent, Web web, string wfName, string scope, string folderPath)
|
|
{
|
|
try
|
|
{
|
|
string fileName = web.Id + "-" + wfName + "-" + scope+".xoml";
|
|
string filePath = folderPath + "\\"+ fileName;
|
|
System.IO.File.WriteAllText(filePath, xamlContent);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Create Data Table
|
|
/// </summary>
|
|
/// <param name="dt"></param>
|
|
public void CreateDataTableColumns(DataTable dt)
|
|
{
|
|
try
|
|
{
|
|
dt.Columns.Add("SiteColID");
|
|
dt.Columns.Add("SiteURL");
|
|
dt.Columns.Add("ListTitle");
|
|
dt.Columns.Add("ListUrl");
|
|
dt.Columns.Add("ContentTypeId");
|
|
dt.Columns.Add("ContentTypeName");
|
|
dt.Columns.Add("Scope");
|
|
dt.Columns.Add("Version");
|
|
dt.Columns.Add("WFTemplateName");
|
|
dt.Columns.Add("WorkFlowName");
|
|
dt.Columns.Add("IsOOBWorkflow");
|
|
// dt.Columns.Add("RelativePath");
|
|
dt.Columns.Add("WFID");
|
|
dt.Columns.Add("WebID");
|
|
dt.Columns.Add("WebURL");
|
|
dt.Columns.Add("Enabled");
|
|
dt.Columns.Add("HasSubscriptions");
|
|
dt.Columns.Add("ConsiderUpgradingToFlow");
|
|
dt.Columns.Add("ToFLowMappingPercentage");
|
|
dt.Columns.Add("UsedActions");
|
|
dt.Columns.Add("ActionCount");
|
|
dt.Columns.Add("AllowManual");
|
|
dt.Columns.Add("AutoStartChange");
|
|
dt.Columns.Add("AutoStartCreate");
|
|
dt.Columns.Add("LastDefinitionModifiedDate");
|
|
dt.Columns.Add("LastSubsrciptionModifiedDate");
|
|
dt.Columns.Add("AssociationData");
|
|
//dt.Columns.Add("Complexity");
|
|
//dt.Columns.Add("ListorLibID");
|
|
//dt.Columns.Add("RelativePath");
|
|
//dt.Columns.Add("IpTemplateName");
|
|
//dt.Columns.Add("InfoPathUsage");
|
|
//dt.Columns.Add("IpID");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create Data Table
|
|
/// </summary>
|
|
/// <param name="dt"></param>
|
|
public void AddRowToDataTable(WorkflowScanResult workflowScanResult, DataTable dt, string version, string scope, string wfName, string wfID, bool IsOOBWF, Web web)
|
|
{
|
|
DataRow dr = dt.NewRow();
|
|
try
|
|
{
|
|
if (workflowScanResult.SiteColUrl == null && web.Url != null)
|
|
{
|
|
dr["SiteURL"] = web.Url;
|
|
dr["SiteColID"] = web.Id;
|
|
}
|
|
else
|
|
dr["SiteURL"] = workflowScanResult.SiteColUrl;
|
|
//dr["WebURL"] = workflowScanResult.SiteURL;
|
|
// dr["CreatedBy"] = workflowScanResult.CreatedBy;
|
|
// dr["ModifiedBy"] = workflowScanResult.ModifiedBy;
|
|
dr["WebURL"] = web.Url;
|
|
dr["ListTitle"] = workflowScanResult.ListTitle;
|
|
dr["ListUrl"] = workflowScanResult.ListUrl;
|
|
dr["ContentTypeId"] = workflowScanResult.ContentTypeId;
|
|
dr["ContentTypeName"] = workflowScanResult.ContentTypeName;
|
|
dr["Scope"] = scope;
|
|
dr["Version"] = version;
|
|
dr["WFTemplateName"] = wfName;
|
|
dr["WorkFlowName"] = workflowScanResult.SubscriptionName;
|
|
dr["IsOOBWorkflow"] = IsOOBWF;
|
|
dr["Enabled"] = workflowScanResult.Enabled; // adding for is enabled
|
|
dr["WFID"] = wfID;
|
|
dr["WebID"] = web.Id;
|
|
dr["HasSubscriptions"] = workflowScanResult.HasSubscriptions; // adding for subscriptions
|
|
string sUsedActions = "";
|
|
// AM need to refactor into a helper function
|
|
if (workflowScanResult.UsedActions != null)
|
|
{
|
|
foreach (var item in workflowScanResult.UsedActions)
|
|
{
|
|
sUsedActions = item.ToString()+";"+ sUsedActions;
|
|
}
|
|
}
|
|
dr["ToFLowMappingPercentage"] = workflowScanResult.ToFLowMappingPercentage; // adding for percentange upgradable to flow
|
|
dr["ConsiderUpgradingToFlow"] = workflowScanResult.ConsiderUpgradingToFlow; // adding for consider upgrading to flow
|
|
dr["UsedActions"] = sUsedActions; // adding for UsedActions
|
|
dr["ActionCount"] = workflowScanResult.ActionCount; // adding for ActionCount
|
|
dr["AllowManual"] = workflowScanResult.AllowManual;
|
|
dr["AutoStartChange"] = workflowScanResult.AutoStartChange;
|
|
dr["AutoStartCreate"] = workflowScanResult.AutoStartCreate;
|
|
dr["LastDefinitionModifiedDate"] = workflowScanResult.LastDefinitionEdit;
|
|
dr["LastSubsrciptionModifiedDate"] = workflowScanResult.LastSubscriptionEdit;
|
|
dr["AssociationData"] = workflowScanResult.AssociationData;
|
|
|
|
|
|
|
|
//dr["Complexity"] = "High"; // adding placeholder for Complexity
|
|
dt.Rows.Add(dr);
|
|
|
|
//dt.Columns.Add("SiteColID");
|
|
//dt.Columns.Add("SiteURL");
|
|
//dt.Columns.Add("ListTitle");
|
|
//dt.Columns.Add("ListUrl");
|
|
//dt.Columns.Add("ContentTypeId");
|
|
//dt.Columns.Add("ContentTypeName");
|
|
//dt.Columns.Add("ItemCount");
|
|
//dt.Columns.Add("Scope");
|
|
//dt.Columns.Add("Version");
|
|
//dt.Columns.Add("WFTemplateName");
|
|
//dt.Columns.Add("IsOOBWorkflow");
|
|
//dt.Columns.Add("RelativePath");
|
|
//dt.Columns.Add("WFID");
|
|
//dt.Columns.Add("WebID");
|
|
//dt.Columns.Add("WebURL");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates 3 levels of folder at the downloaded path provided by the user
|
|
/// 1. Analysis
|
|
/// 2. DownloadedForms
|
|
/// 3. Summary
|
|
/// </summary>
|
|
/// <param name="downloadPath"></param>
|
|
public void CreateDirectoryStructure(string downloadPath)
|
|
{
|
|
try
|
|
{
|
|
if (!Directory.Exists(string.Concat(downloadPath, analysisFolder)))
|
|
{
|
|
DirectoryInfo analysisFolder1 = System.IO.Directory.CreateDirectory(string.Concat(downloadPath, analysisFolder));
|
|
Logging.GetInstance().WriteToLogFile(Logging.Info, "Analysis folder created");
|
|
}
|
|
if (!Directory.Exists(string.Concat(downloadPath, downloadedFormsFolder)))
|
|
{
|
|
DirectoryInfo downloadedFormsFolder1 = System.IO.Directory.CreateDirectory(string.Concat(downloadPath, downloadedFormsFolder));
|
|
Logging.GetInstance().WriteToLogFile(Logging.Info, "DownloadedForms folder created");
|
|
|
|
}
|
|
if (!Directory.Exists(string.Concat(downloadPath, summaryFolder)))
|
|
{
|
|
DirectoryInfo summaryFolder1 = System.IO.Directory.CreateDirectory(string.Concat(downloadPath, summaryFolder));
|
|
Logging.GetInstance().WriteToLogFile(Logging.Info, "Summary folder created");
|
|
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
}
|
|
}
|
|
|
|
public DataTable ConvertCSVToDataTable(string csvLocation)
|
|
{
|
|
DataTable dt = new DataTable();
|
|
try
|
|
{
|
|
var Lines = System.IO.File.ReadAllLines(csvLocation);
|
|
string[] Fields;
|
|
Fields = Lines[0].Split(new char[] { ',' });
|
|
int Cols = Fields.GetLength(0);
|
|
for (int i = 0; i < Cols; i++)
|
|
dt.Columns.Add(Fields[i].ToLower(), typeof(string));
|
|
DataRow Row;
|
|
for (int i = 1; i < Lines.GetLength(0); i++)
|
|
{
|
|
Fields = Lines[i].Split(new char[] { ',' });
|
|
Row = dt.NewRow();
|
|
for (int f = 0; f < Cols; f++)
|
|
Row[f] = Fields[f].Split('"')[1];
|
|
dt.Rows.Add(Row);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
}
|
|
return dt;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The DataSet returned from the content database is stored in a datatable
|
|
/// The datatable is then saved into a CSV file that gets stored in the Summary folder
|
|
/// that gets created at the location of download path supplied by the users
|
|
/// </summary>
|
|
/// <param name="dataTable"></param>
|
|
/// <param name="filePath"></param>
|
|
public void WriteToCsvFile(DataTable dataTable, string filePath)
|
|
{
|
|
try
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Info, "Preparing to create the CSV at " + filePath);
|
|
StringBuilder fileContent = new StringBuilder();
|
|
|
|
foreach (var col in dataTable.Columns)
|
|
{
|
|
fileContent.Append(col.ToString() + ",");
|
|
}
|
|
|
|
fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
|
|
|
|
foreach (DataRow dr in dataTable.Rows)
|
|
{
|
|
foreach (var column in dr.ItemArray)
|
|
{
|
|
fileContent.Append("\"" + column.ToString() + "\",");
|
|
}
|
|
|
|
fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
|
|
}
|
|
|
|
System.IO.File.WriteAllText(filePath, fileContent.ToString());
|
|
Logging.GetInstance().WriteToLogFile(Logging.Info, string.Format("CSV File created at {0}", filePath));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="s"></param>
|
|
/// <param name="x"></param>
|
|
/// <param name="y"></param>
|
|
public void WriteProgress(string s, int x, int y)
|
|
{
|
|
int origRow = Console.CursorTop;
|
|
int origCol = Console.CursorLeft;
|
|
// Console.WindowWidth = 10; // this works.
|
|
int width = Console.WindowWidth;
|
|
//x = x % width;
|
|
try
|
|
{
|
|
Console.SetCursorPosition(x, y);
|
|
//Console.SetCursorPosition(origCol, origRow);
|
|
Console.Write(s);
|
|
}
|
|
catch (ArgumentOutOfRangeException e)
|
|
{
|
|
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
Console.SetCursorPosition(origRow, origCol);
|
|
}
|
|
catch (ArgumentOutOfRangeException e)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
internal ClientContext CreateClientContext(string url, string username, SecureString password)
|
|
{
|
|
try
|
|
{
|
|
var credentials = new SharePointOnlineCredentials(
|
|
username,
|
|
password);
|
|
|
|
return new ClientContext(url)
|
|
{
|
|
Credentials = credentials
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
return new ClientContext(url)
|
|
{
|
|
};
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
public List<string> GetAllTenantSites(string TenantName, PSCredential Credential)
|
|
{
|
|
List<string> sites = new List<string>();
|
|
try
|
|
{
|
|
string tenantAdminUrl = "https://" + TenantName + "-admin.sharepoint.com/";
|
|
ClientContext ctx = null;
|
|
ctx = CreateClientContext(tenantAdminUrl, Credential.UserName, Credential.Password);
|
|
Tenant tenant = new Tenant(ctx);
|
|
SPOSitePropertiesEnumerable siteProps = tenant.GetSitePropertiesFromSharePoint("0", true);
|
|
ctx.Load(siteProps);
|
|
ctx.ExecuteQuery();
|
|
int count = 0;
|
|
foreach (var site in siteProps)
|
|
{
|
|
sites.Add(site.Url);
|
|
count++;
|
|
}
|
|
Console.WriteLine("Total Site {0}", count);
|
|
return sites;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
|
|
}
|
|
return sites;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open the site collection file and store in a collection variable
|
|
/// Read the file and display it line by line.
|
|
/// </summary>
|
|
/// <param name="sitecollectionUrls"></param>
|
|
public void ReadInfoPathOnlineSiteCollection(List<string> sitecollectionUrls, string filePath)
|
|
{
|
|
try
|
|
{
|
|
int counter = 0;
|
|
string line;
|
|
System.IO.StreamReader file =
|
|
new System.IO.StreamReader(filePath);
|
|
while ((line = file.ReadLine()) != null)
|
|
{
|
|
//removes all extra spaces etc.
|
|
sitecollectionUrls.Add(line.TrimEnd());
|
|
//System.Console.WriteLine(line);
|
|
counter++;
|
|
}
|
|
file.Close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
public class InfoPathScanResult
|
|
{
|
|
public string SiteColUrl { get; set; }
|
|
public string SiteURL { get; set; }
|
|
public string InfoPathUsage { get; set; }
|
|
public string ListTitle { get; set; }
|
|
public string ListId { get; set; }
|
|
public string ListUrl { get; set; }
|
|
public bool Enabled { get; set; }
|
|
public string InfoPathTemplate { get; set; }
|
|
public int ItemCount { get; set; }
|
|
public DateTime LastItemUserModifiedDate { get; set; }
|
|
|
|
public string strDirName { get; set; }
|
|
|
|
public string strLeafName { get; set; }
|
|
|
|
public string SiteID { get; set; }
|
|
|
|
public string WebID { get; set; }
|
|
}
|
|
|
|
|
|
public class Config
|
|
{
|
|
public string Rule { get; set; }
|
|
public Complexity Complexity { get; set; }
|
|
public bool MigrationPathExists { get; set; }
|
|
public bool CustomConnector { get; set; }
|
|
|
|
}
|
|
public enum Complexity { Low, Medium, High, Critical };
|
|
|
|
public enum Scope { Farm, WebApp, SiteCol };
|
|
|
|
public abstract class InfoPathFeature
|
|
{
|
|
#region Useful XNamespace values
|
|
protected static XNamespace xdNamespace = @"http://schemas.microsoft.com/office/infopath/2003";
|
|
protected static XNamespace xsfNamespace = @"http://schemas.microsoft.com/office/infopath/2003/solutionDefinition";
|
|
protected static XNamespace xsf2Namespace = @"http://schemas.microsoft.com/office/infopath/2006/solutionDefinition/extensions";
|
|
protected static XNamespace xsf3Namespace = @"http://schemas.microsoft.com/office/infopath/2009/solutionDefinition/extensions";
|
|
protected static XNamespace xslNamespace = @"http://www.w3.org/1999/XSL/Transform";
|
|
#endregion
|
|
|
|
#region Public interface
|
|
public string FeatureName { get { return this.GetType().Name; } }
|
|
// need virtuals for formatting as a string, and an xml, or something ...
|
|
public override string ToString()
|
|
{
|
|
return FeatureName;
|
|
}
|
|
#endregion
|
|
public Complexity Complexity { get; set; }
|
|
public bool CustomConnector { get; set; }
|
|
public bool MigrationPathExists { get; set; }
|
|
public List<Config> Rules { get; set; }
|
|
|
|
private void GetConfigFromJSON(JArray configuration)
|
|
{
|
|
foreach (dynamic dataconnection in configuration)
|
|
{
|
|
try
|
|
{
|
|
Rules.Add(new Config()
|
|
{
|
|
Rule = dataconnection.name,
|
|
Complexity = dataconnection.Complexity,
|
|
MigrationPathExists = dataconnection.migrationPathExists,
|
|
CustomConnector = dataconnection.CustomConnector
|
|
});
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static string LoadDebugJSON()
|
|
{
|
|
string json = string.Empty;
|
|
string dir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
|
|
|
using (StreamReader r = new StreamReader(dir + @"\infopathrules.json"))
|
|
|
|
//using (StreamReader r = new StreamReader(@"C:\Users\rmeure\source\repos\InfoPathScrapper\InfoPathScrapper\settings.json"))
|
|
{
|
|
json = r.ReadToEnd();
|
|
}
|
|
return json;
|
|
}
|
|
public InfoPathFeature()
|
|
{
|
|
Rules = new List<Config>();
|
|
|
|
var settings = LoadDebugJSON();
|
|
JObject configuration = JObject.Parse(settings);
|
|
dynamic obj = JObject.Parse(settings);
|
|
|
|
GetConfigFromJSON((JArray)obj["dataConnections"]["dataConnection"]);
|
|
GetConfigFromJSON((JArray)obj["dataRules"]["dataRule"]);
|
|
GetConfigFromJSON((JArray)obj["dataRules"]["actions"]["action"]);
|
|
GetConfigFromJSON((JArray)obj["controls"]["Control"]);
|
|
}
|
|
|
|
#region Abstract method(s)
|
|
/// <summary>
|
|
/// This method returns a commaseparated list of the interesting values a particular feature has collected.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public abstract string ToCSV();
|
|
#endregion
|
|
}
|
|
|
|
public class Control : InfoPathFeature
|
|
{
|
|
#region Private stuff
|
|
private const string xctName = @"xctname";
|
|
|
|
private Control() { }
|
|
#endregion
|
|
|
|
#region Public interface
|
|
public string Name { get; private set; }
|
|
public int Count { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Instead of logging on feature per control, I do 1 feature per control type along with the number of occurrences
|
|
/// </summary>
|
|
/// <param name="document"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
IEnumerable<XElement> allElements = document.Descendants();
|
|
BucketCounter counter = new BucketCounter();
|
|
// collect the control counts
|
|
foreach (XElement element in allElements)
|
|
{
|
|
XAttribute xctAttribute = element.Attribute(xdNamespace + xctName);
|
|
if (xctAttribute != null)
|
|
{
|
|
counter.IncrementKey(xctAttribute.Value);
|
|
}
|
|
}
|
|
|
|
// then create Control objects for each control
|
|
foreach (KeyValuePair<string, int> kvp in counter.Buckets)
|
|
{
|
|
Control c = new Control();
|
|
c.Name = kvp.Key;
|
|
c.Count = kvp.Value;
|
|
yield return c;
|
|
}
|
|
// nothing left
|
|
yield break;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + Name + "[" + Count + "]";
|
|
//return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + Name + "[" + Count + "]";
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return Name + "," + Count;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
|
|
|
|
public class InfoPathLocation
|
|
{
|
|
public string SiteId { get; set; }
|
|
public string WebId { get; set; }
|
|
public string DocId { get; set; }
|
|
public string DirName { get; set; }
|
|
public string LeafName { get; set; }
|
|
|
|
}
|
|
|
|
// Data Connection Classes
|
|
public class DataConnection : InfoPathFeature
|
|
{
|
|
#region Constants
|
|
private const string query = @"query";
|
|
private const string spListConnection = @"sharepointListAdapter";
|
|
private const string spListConnectionRW = @"sharepointListAdapterRW";
|
|
private const string soapConnection = @"webServiceAdapter";
|
|
private const string xmlConnection = @"xmlFileAdapter"; // also used for REST!
|
|
private const string adoConnection = @"adoAdapter";
|
|
#endregion
|
|
|
|
#region Public interface
|
|
public string ConnectionType { get; private set; }
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
|
|
IEnumerable<XElement> allDataConnections = document.Descendants(xsfNamespace + query);
|
|
foreach (XElement queryElement in allDataConnections)
|
|
{
|
|
yield return ParseDataConnection(queryElement);
|
|
}
|
|
|
|
// nothing left
|
|
yield break;
|
|
}
|
|
|
|
public string Connection
|
|
{ get; private set; }
|
|
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + ConnectionType;
|
|
//return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + ConnectionType;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return ConnectionType;
|
|
}
|
|
#endregion
|
|
|
|
#region Private helpers
|
|
/// <summary>
|
|
/// This should return DataConnection since every query element represents exactly one connection
|
|
/// In special cases (SPList, Soap) we defer to a subclass to mine more data.
|
|
/// </summary>
|
|
/// <param name="queryElement"></param>
|
|
/// <returns></returns>
|
|
private static DataConnection ParseDataConnection(XElement queryElement)
|
|
{
|
|
XElement dataConnection = queryElement.Element(xsfNamespace + spListConnection);
|
|
if (dataConnection != null)
|
|
return SPListConnection.Parse(dataConnection);
|
|
else if ((dataConnection = queryElement.Element(xsfNamespace + spListConnectionRW)) != null)
|
|
return SPListConnection.Parse(dataConnection);
|
|
else if ((dataConnection = queryElement.Element(xsfNamespace + soapConnection)) != null)
|
|
return SoapConnection.Parse(dataConnection);
|
|
else if ((dataConnection = queryElement.Element(xsfNamespace + xmlConnection)) != null)
|
|
return XmlConnection.Parse(dataConnection);
|
|
else if ((dataConnection = queryElement.Element(xsfNamespace + adoConnection)) != null)
|
|
return AdoConnection.Parse(dataConnection);
|
|
|
|
// else just grab the type and log that. Nothing else to do here.
|
|
foreach (XElement x in queryElement.Elements())
|
|
{
|
|
if (dataConnection != null) throw new ArgumentException("More than one adapter found under a query node");
|
|
dataConnection = x;
|
|
}
|
|
|
|
if (dataConnection == null) throw new ArgumentException("No adapter found under query node");
|
|
DataConnection dc = new DataConnection();
|
|
dc.ConnectionType = dataConnection.Name.LocalName;
|
|
return dc;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subclass specifically for mining SP List connections
|
|
/// </summary>
|
|
class SPListConnection : DataConnection
|
|
{
|
|
#region Constants
|
|
private const string siteUrlAttribute = @"siteUrl";
|
|
private const string siteURLAttribute = @"siteURL";
|
|
private const string listGuidAttribute = @"sharePointListID";
|
|
private const string sharepointGuidAttribute = @"sharepointGuid";
|
|
private const string submitAllowedAttribute = @"submitAllowed";
|
|
private const string relativeListUrlAttribute = @"relativeListUrl";
|
|
private const string field = @"field";
|
|
private const string typeAttribute = @"type";
|
|
private static string[] validTypes =
|
|
{
|
|
"Counter",
|
|
"Integer",
|
|
"Number",
|
|
"Currency",
|
|
"Text",
|
|
"Choice",
|
|
"Plain",
|
|
"Compatible",
|
|
"FullHTML",
|
|
"DateTime",
|
|
"Boolean",
|
|
"Lookup",
|
|
"LookupMulti",
|
|
"MultiChoice",
|
|
"URL",
|
|
"User",
|
|
"UserMulti",
|
|
"Calculated",
|
|
"Attachments",
|
|
"HybridUser",
|
|
};
|
|
private BucketCounter _bucketCounter;
|
|
#endregion
|
|
|
|
private SPListConnection()
|
|
{
|
|
|
|
var rule = (from r in Rules
|
|
where r.Rule == this.GetType().Name
|
|
select r).First();
|
|
this.Complexity = rule.Complexity;
|
|
this.MigrationPathExists = rule.MigrationPathExists;
|
|
this.CustomConnector = rule.CustomConnector;
|
|
|
|
_bucketCounter = new BucketCounter();
|
|
foreach (string type in validTypes)
|
|
_bucketCounter.DefineKey(type);
|
|
}
|
|
|
|
#region Public interface
|
|
|
|
public string SiteUrl { get; private set; }
|
|
public string ListGuid { get; private set; }
|
|
public string SubmitAllowed { get; private set; }
|
|
public string RelativeListUrl { get; private set; }
|
|
public IEnumerable<KeyValuePair<string, int>> ColumnTypes
|
|
{
|
|
get
|
|
{
|
|
foreach (KeyValuePair<string, int> kvp in _bucketCounter.Buckets)
|
|
yield return kvp;
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
//sb.Append("Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " ");
|
|
sb.Append(FeatureName).Append(": ");
|
|
sb.Append("{").Append(SiteUrl).Append(", ").Append(RelativeListUrl).Append("} ");
|
|
if (IsV2)
|
|
{
|
|
sb.Append("Types: {");
|
|
foreach (KeyValuePair<string, int> kvp in ColumnTypes)
|
|
{
|
|
// for humanreadable, just emit nonzero counts
|
|
if (kvp.Value > 0)
|
|
sb.Append(kvp.Key).Append("=").Append(kvp.Value).Append(" ");
|
|
}
|
|
sb.Append("}");
|
|
}
|
|
return sb.ToString().Trim();
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append(SiteUrl).Append(",").Append(RelativeListUrl).Append(",");
|
|
if (IsV2)
|
|
{
|
|
foreach (KeyValuePair<string, Int32> kvp in ColumnTypes)
|
|
{
|
|
sb.Append(kvp.Key).Append(",").Append(kvp.Value).Append(",");
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
private bool IsV2 { get; set; }
|
|
|
|
/// <summary>
|
|
/// Selfexplanatory
|
|
/// </summary>
|
|
/// <param name="dataConnection"></param>
|
|
/// <returns></returns>
|
|
public static SPListConnection Parse(XElement dataConnection)
|
|
{
|
|
SPListConnection spl = new SPListConnection();
|
|
spl.IsV2 = dataConnection.Name.LocalName.Equals("sharepointListAdapterRW");
|
|
if (spl.IsV2)
|
|
{
|
|
spl.SiteUrl = dataConnection.Attribute(siteURLAttribute).Value;
|
|
spl.ListGuid = dataConnection.Attribute(listGuidAttribute).Value;
|
|
spl.SubmitAllowed = dataConnection.Attribute(submitAllowedAttribute).Value;
|
|
spl.RelativeListUrl = dataConnection.Attribute(relativeListUrlAttribute).Value;
|
|
|
|
// we should also scrape out the queried column types, this shows us what types of data we consume from lists
|
|
foreach (XElement fieldElement in dataConnection.Elements(xsfNamespace + field))
|
|
{
|
|
string fieldType = fieldElement.Attribute(typeAttribute).Value;
|
|
spl._bucketCounter.IncrementKey(fieldType);
|
|
}
|
|
}
|
|
else // if (dataConnection.Name.LocalName.Equals("sharepointListAdapter"))
|
|
{
|
|
spl.SiteUrl = dataConnection.Attribute(siteUrlAttribute).Value;
|
|
spl.ListGuid = dataConnection.Attribute(sharepointGuidAttribute).Value;
|
|
spl.SubmitAllowed = dataConnection.Attribute(submitAllowedAttribute).Value;
|
|
}
|
|
|
|
|
|
return spl;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subclass of DataConnection specifically for Soap web service calls.
|
|
/// </summary>
|
|
class SoapConnection : DataConnection
|
|
{
|
|
#region Constants
|
|
private const string wsdlUrlAttribute = @"wsdlUrl";
|
|
private const string serviceUrlAttribute = @"serviceUrl";
|
|
private const string nameAttribute = @"name";
|
|
private const string operation = @"operation";
|
|
private const string input = @"input";
|
|
private const string sourceAttribute = @"source";
|
|
|
|
private const string webServiceAdapterExtension = @"webServiceAdapterExtension";
|
|
private const string refAttribute = @"ref";
|
|
private const string connectoid = @"connectoid";
|
|
private const string udcxExt = @".udcx";
|
|
#endregion
|
|
|
|
private SoapConnection()
|
|
{
|
|
var rule = (from r in Rules
|
|
where r.Rule == this.GetType().Name
|
|
select r).First();
|
|
this.Complexity = rule.Complexity;
|
|
this.MigrationPathExists = rule.MigrationPathExists;
|
|
this.CustomConnector = rule.CustomConnector;
|
|
}
|
|
|
|
#region Public interface
|
|
public string ServiceUrl { get; private set; }
|
|
public string ServiceMethod { get; private set; }
|
|
|
|
public static DataConnection Parse(XElement dataConnection)
|
|
{
|
|
XElement udcExtension = null;
|
|
if (IsConnectionUDCX(dataConnection, out udcExtension) && udcExtension != null)
|
|
{
|
|
return UdcConnection.Parse(dataConnection, udcExtension);
|
|
}
|
|
return ParseInternal(dataConnection);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + ServiceUrl + "::" + ServiceMethod;
|
|
//return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + ServiceUrl + "::" + ServiceMethod;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return ServiceUrl + "," + ServiceMethod;
|
|
}
|
|
#endregion
|
|
|
|
#region Private helpers
|
|
private static SoapConnection ParseInternal(XElement dataConnection)
|
|
{
|
|
SoapConnection sc = new SoapConnection();
|
|
XElement op = dataConnection.Element(xsfNamespace + operation);
|
|
if (op == null)
|
|
{
|
|
sc.ServiceUrl = dataConnection.Attribute(wsdlUrlAttribute).Value;
|
|
sc.ServiceMethod = dataConnection.Attribute(nameAttribute).Value;
|
|
}
|
|
else
|
|
{
|
|
sc.ServiceUrl = op.Attribute(serviceUrlAttribute).Value;
|
|
sc.ServiceMethod = op.Attribute(nameAttribute).Value;
|
|
XElement inp = op.Element(xsfNamespace + input);
|
|
if (inp != null && sc.ServiceUrl.Equals(""))
|
|
{
|
|
sc.ServiceUrl = inp.Attribute(sourceAttribute).Value;
|
|
sc.ServiceMethod = "?";
|
|
}
|
|
}
|
|
|
|
|
|
if (sc.ServiceUrl.Equals("")) Console.WriteLine(dataConnection.ToString());
|
|
return sc;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Need to find the xsf2:webServiceAdapterExtension node elsewhere in the XDocument
|
|
/// Need to find the one that has ref the same as our connection name
|
|
/// </summary>
|
|
/// <param name="dataConnection"></param>
|
|
/// <returns></returns>
|
|
private static bool IsConnectionUDCX(XElement dataConnection, out XElement udcExtension)
|
|
{
|
|
udcExtension = null;
|
|
XAttribute name = dataConnection.Attribute(nameAttribute);
|
|
if (name == null) return false;
|
|
|
|
string connectionName = name.Value;
|
|
foreach (XElement webServiceExt in dataConnection.Document.Descendants(xsf2Namespace + webServiceAdapterExtension))
|
|
{
|
|
XAttribute refAtt = webServiceExt.Attribute(refAttribute);
|
|
if (refAtt == null) continue; // No name = no match
|
|
if (!refAtt.Value.Equals(connectionName)) continue; // These are not the extensions you are looking for ... *waves hand*
|
|
|
|
XElement connect = webServiceExt.Element(xsf2Namespace + connectoid);
|
|
if (connect == null) return false;
|
|
XAttribute source = connect.Attribute(sourceAttribute);
|
|
if (source == null) return false;
|
|
|
|
if (Path.GetExtension(source.Value) != udcxExt) return false;
|
|
|
|
udcExtension = webServiceExt;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subclass of DataConnection specifically for UDCX Soap web service calls.
|
|
/// </summary>
|
|
class UdcConnection : DataConnection
|
|
{
|
|
#region Constants
|
|
private const string connectoid = @"connectoid";
|
|
private const string nameAttribute = @"name";
|
|
private const string sourceAttribute = @"source";
|
|
#endregion
|
|
|
|
//private UdcConnection() { }
|
|
|
|
#region Public interface
|
|
public string SourceUrl { get; private set; }
|
|
public string MethodName { get; private set; }
|
|
|
|
public static UdcConnection Parse(XElement dataConnection, XElement udcExtension)
|
|
{
|
|
UdcConnection uc = new UdcConnection();
|
|
|
|
XElement connect = udcExtension.Element(xsf2Namespace + connectoid);
|
|
|
|
uc.MethodName = connect.Attribute(nameAttribute).Value;
|
|
uc.SourceUrl = connect.Attribute(sourceAttribute).Value;
|
|
|
|
|
|
return uc;
|
|
}
|
|
|
|
protected UdcConnection()
|
|
{
|
|
var rule = (from r in Rules
|
|
where r.Rule == this.GetType().Name
|
|
select r).First();
|
|
this.Complexity = rule.Complexity;
|
|
this.MigrationPathExists = rule.MigrationPathExists;
|
|
this.CustomConnector = rule.CustomConnector;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + SourceUrl + "::" + MethodName;
|
|
//return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + SourceUrl + "::" + MethodName;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return SourceUrl + "," + MethodName;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Identifies a connection to an Xml file
|
|
/// </summary>
|
|
class XmlConnection : DataConnection
|
|
{
|
|
#region Constants
|
|
private const string fileUrlAttribute = @"fileUrl";
|
|
private const string nameAttribute = @"name";
|
|
private const string refAttribute = @"ref";
|
|
private const string xmlFileAdapterExtension = @"xmlFileAdapterExtension";
|
|
private const string isRestAttribute = @"isRest";
|
|
#endregion
|
|
|
|
protected XmlConnection()
|
|
{
|
|
var rule = (from r in Rules
|
|
where r.Rule == this.GetType().Name
|
|
select r).First();
|
|
this.Complexity = rule.Complexity;
|
|
this.MigrationPathExists = rule.MigrationPathExists;
|
|
this.CustomConnector = rule.CustomConnector;
|
|
}
|
|
|
|
#region Public interface
|
|
public string Url { get; private set; }
|
|
|
|
public static DataConnection Parse(XElement dataConnection)
|
|
{
|
|
XmlConnection xc = null;
|
|
bool isRest = IsConnectionRest(dataConnection);
|
|
xc = isRest ? new RESTConnection() : new XmlConnection();
|
|
|
|
string fileUrl = dataConnection.Attribute(fileUrlAttribute).Value;
|
|
|
|
if (!String.IsNullOrEmpty(fileUrl))
|
|
{
|
|
// We have an embedded XmlConnection
|
|
xc.Url = dataConnection.Attribute(fileUrlAttribute).Value;
|
|
return xc;
|
|
}
|
|
else
|
|
{
|
|
// The XmlConnection is stored in a UDCX connection file
|
|
XElement udcExtension = FindChild(dataConnection);
|
|
return UdcConnection.Parse(dataConnection, udcExtension);
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + Url;
|
|
//return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + Url;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return Url;
|
|
}
|
|
#endregion
|
|
|
|
#region Private helpers
|
|
private static XElement FindChild(XElement dataConnection)
|
|
{
|
|
XAttribute name = dataConnection.Attribute(nameAttribute);
|
|
if (name == null) return null;
|
|
|
|
string connectionName = name.Value;
|
|
foreach (XElement xmlExt in dataConnection.Document.Descendants(xsf2Namespace + xmlFileAdapterExtension))
|
|
{
|
|
XAttribute refAtt = xmlExt.Attribute(refAttribute);
|
|
if (refAtt == null) continue; // No name = no match
|
|
if (!refAtt.Value.Equals(connectionName)) continue; // These are not the extensions you are looking for ... *waves hand*
|
|
return xmlExt;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Need to find the xsf2:xmlFileAdapterExtension node elsewhere in the XDocument
|
|
/// Need to find the one that has ref the same as our connection name, then look for isRest="[bool]" in there
|
|
/// </summary>
|
|
/// <param name="dataConnection"></param>
|
|
/// <returns></returns>
|
|
private static bool IsConnectionRest(XElement dataConnection)
|
|
{
|
|
XElement xmlExt = FindChild(dataConnection);
|
|
if (xmlExt == null) return false;
|
|
|
|
XAttribute isRest = xmlExt.Attribute(isRestAttribute);
|
|
if (isRest == null) return false;
|
|
|
|
return isRest.Value.Equals("yes");
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
class RESTConnection : XmlConnection
|
|
{
|
|
//private RestConnection()
|
|
//{
|
|
/// this.Complexity =Complexity.High;
|
|
// this.CustomConnector = true;
|
|
//}
|
|
}
|
|
|
|
class AdoConnection : DataConnection
|
|
{
|
|
#region Constants
|
|
private const string connectionStringAttribute = @"connectionString";
|
|
#endregion
|
|
|
|
private AdoConnection()
|
|
{
|
|
var rule = (from r in Rules
|
|
where r.Rule == this.GetType().Name
|
|
select r).First();
|
|
this.Complexity = rule.Complexity;
|
|
this.MigrationPathExists = rule.MigrationPathExists;
|
|
this.CustomConnector = rule.CustomConnector;
|
|
}
|
|
|
|
#region Public interface
|
|
public string ConnectionString { get; private set; }
|
|
|
|
public static AdoConnection Parse(XElement dataConnection)
|
|
{
|
|
AdoConnection ac = new AdoConnection();
|
|
ac.ConnectionString = dataConnection.Attribute(connectionStringAttribute).Value;
|
|
ac.Complexity = Complexity.High;
|
|
return ac;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + ConnectionString;
|
|
// return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + ConnectionString;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return ConnectionString;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
|
|
// Data Rules Classess
|
|
|
|
public class DataRule : InfoPathFeature
|
|
{
|
|
#region Constants
|
|
private const string rule = @"rule";
|
|
#endregion
|
|
|
|
#region Public interface
|
|
public string ActionType { get; private set; }
|
|
|
|
public DataRule()
|
|
{
|
|
var rule = (from r in Rules
|
|
where r.Rule == this.GetType().Name
|
|
select r).First();
|
|
|
|
this.Complexity = rule.Complexity;
|
|
this.MigrationPathExists = rule.MigrationPathExists;
|
|
this.CustomConnector = rule.CustomConnector;
|
|
}
|
|
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
IEnumerable<XElement> allRules = document.Descendants(xsfNamespace + rule);
|
|
foreach (XElement ruleElement in allRules)
|
|
{
|
|
foreach (DataRule feature in ParseRuleElement(ruleElement))
|
|
yield return feature;
|
|
}
|
|
yield break;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + ActionType;
|
|
// return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + ActionType;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return ActionType;
|
|
}
|
|
#endregion
|
|
|
|
#region Private helpers
|
|
private static IEnumerable<DataRule> ParseRuleElement(XElement ruleElement)
|
|
{
|
|
foreach (XElement ruleAction in ruleElement.Elements())
|
|
{
|
|
DataRule feature = new DataRule();
|
|
feature.ActionType = ruleAction.Name.LocalName;
|
|
// we can be any one of many types of rules: dialogbox, assignment, query, submit, switch view
|
|
// we could parse further if that turns out to be interesting
|
|
yield return feature;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
// Data Validation Class
|
|
|
|
public class DataValidation : InfoPathFeature
|
|
{
|
|
#region Constants
|
|
private const string customValidation = @"customValidation";
|
|
#endregion
|
|
|
|
#region Public interface
|
|
public string ValidationType { get; private set; }
|
|
private DataValidation()
|
|
{
|
|
var rule = (from r in Rules
|
|
where r.Rule == this.GetType().Name
|
|
select r).First();
|
|
this.Complexity = rule.Complexity;
|
|
this.MigrationPathExists = rule.MigrationPathExists;
|
|
this.CustomConnector = rule.CustomConnector;
|
|
}
|
|
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
// we don't care about the condition details, just "any custom validation" vs "native cbb"
|
|
IEnumerable<XElement> allValidations = document.Descendants(xsfNamespace + customValidation);
|
|
foreach (XElement validationElement in allValidations)
|
|
{
|
|
DataValidation validation = new DataValidation();
|
|
validation.ValidationType = "Custom validation";
|
|
yield return validation;
|
|
}
|
|
|
|
allValidations = document.Descendants(xsf3Namespace + customValidation);
|
|
foreach (XElement validationElement in allValidations)
|
|
{
|
|
DataValidation validation = new DataValidation();
|
|
validation.ValidationType = "Cannot be blank";
|
|
yield return validation;
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + ValidationType;
|
|
//return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + ValidationType;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return ValidationType;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
// Formatting Class
|
|
|
|
public class FormattingRule : InfoPathFeature
|
|
{
|
|
#region Constants
|
|
private const string xslAttribute = @"attribute";
|
|
private const string nameAttribute = @"name";
|
|
private const string contentEditable = @"contentEditable"; // readonly
|
|
private const string style = @"style"; // adjusting the look and feel (colors, hiding, etc ...)
|
|
private const string when = @"when";
|
|
#endregion
|
|
|
|
private FormattingRule()
|
|
{
|
|
this.Complexity = Complexity.Medium;
|
|
this.CustomConnector = false;
|
|
this.MigrationPathExists = true;
|
|
}
|
|
|
|
#region Public interface
|
|
public string FormatType { get; private set; }
|
|
public string SubDetails { get; private set; }
|
|
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
IEnumerable<XElement> allElements = document.Descendants(xslNamespace + xslAttribute);
|
|
foreach (XElement element in allElements)
|
|
{
|
|
XAttribute name = element.Attribute(nameAttribute);
|
|
// these are the html attributes that we try to set.
|
|
// specifically for conditional hide we have to look under a style for an xsl:when with .Text() contains "DISPLAY: none"
|
|
if (name.Value.Equals(contentEditable))
|
|
{
|
|
FormattingRule rule = new FormattingRule();
|
|
rule.FormatType = "Readonly";
|
|
yield return rule;
|
|
}
|
|
else if (name.Value.Equals(style))
|
|
{
|
|
BucketCounter counter = new BucketCounter();
|
|
FormattingRule rule = new FormattingRule();
|
|
rule.FormatType = "Style";
|
|
// now let's count all the things we're affecting.
|
|
// Overloading BucketCounter to filter the noise of multiple touches to same style
|
|
foreach (XElement xslWhen in element.Descendants(xslNamespace + when))
|
|
{
|
|
string[] styles = xslWhen.Value.Split(new char[] { ';' });
|
|
foreach (string s in styles)
|
|
{
|
|
if (s.Trim().StartsWith("caption:")) continue;
|
|
string affectedStyle = s.Split(':')[0].Trim().ToUpper();
|
|
counter.IncrementKey(affectedStyle);
|
|
}
|
|
}
|
|
StringBuilder sb = new StringBuilder();
|
|
foreach (KeyValuePair<string, int> kvp in counter.Buckets)
|
|
{
|
|
sb.Append(kvp.Key).Append(" ");
|
|
}
|
|
rule.SubDetails = sb.ToString().Trim();
|
|
yield return rule;
|
|
}
|
|
}
|
|
|
|
// nothing left
|
|
yield break;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + FormatType + (SubDetails == null ? "" : "," + SubDetails);
|
|
// return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + FormatType + (SubDetails == null ? "" : " " + SubDetails);
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return FormatType + (SubDetails == null ? "" : "," + SubDetails);
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
// Managed Code Class
|
|
|
|
class ManagedCode : InfoPathFeature
|
|
{
|
|
private const string enabledAttribute = @"enabled";
|
|
private const string languageAttribute = @"language";
|
|
private const string versionAttribute = @"version";
|
|
private const string managedCode = @"managedCode";
|
|
|
|
private ManagedCode()
|
|
{
|
|
this.Complexity = Complexity.High;
|
|
this.CustomConnector = false;
|
|
this.MigrationPathExists = false;
|
|
}
|
|
|
|
public string Language { get; private set; }
|
|
public string Version { get; private set; }
|
|
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
IEnumerable<XElement> allElements = document.Descendants(xsf2Namespace + managedCode);
|
|
foreach (XElement element in allElements)
|
|
{
|
|
//The enabled attribute is typically null, so check for that.
|
|
//If the attribute exists, then we can also check the value to make sure there's not an enabled="no" scenario.
|
|
if (element.Attribute(enabledAttribute) == null
|
|
|| !string.Equals(element.Attribute(enabledAttribute).Value, "yes", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Return an object if enabled="yes"
|
|
ManagedCode mc = new ManagedCode();
|
|
mc.Language = element.Attribute(languageAttribute).Value;
|
|
mc.Version = element.Attribute(versionAttribute).Value;
|
|
yield return mc;
|
|
}
|
|
|
|
// nothing left
|
|
yield break;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + Language + " " + Version;
|
|
//return "Complexity: " + Complexity + " CustomConnector: " + CustomConnector + " MigrationPathExists: " + MigrationPathExists + " " + FeatureName + ": " + Language + " " + Version;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return Language + "," + Version;
|
|
}
|
|
}
|
|
|
|
//Mode Class
|
|
|
|
class Mode : InfoPathFeature
|
|
{
|
|
private const string modeAttribute = @"mode";
|
|
private const string solutionFormatVersionAttribute = @"solutionFormatVersion";
|
|
private const string solutionMode = @"solutionMode";
|
|
private const string solutionDefinition = @"solutionDefinition";
|
|
private const string solutionPropertiesExtension = @"solutionPropertiesExtension";
|
|
private const string branchAttribute = @"branch";
|
|
private const string runtimeCompatibilityAttribute = @"runtimeCompatibility";
|
|
private const string xDocumentClass = @"xDocumentClass";
|
|
|
|
public string ModeName { get; private set; }
|
|
public string Compatibility { get; private set; }
|
|
|
|
private Mode() { }
|
|
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
|
|
Mode m = new Mode();
|
|
string mode = null;
|
|
|
|
// look for fancy new modes first (these were new for xsf3 / IP2010)
|
|
IEnumerable<XElement> allModeElements = document.Descendants(xsf3Namespace + solutionMode);
|
|
foreach (XElement element in allModeElements)
|
|
{
|
|
if (mode != null) throw new ArgumentException("Found more than one mode!");
|
|
XAttribute name = element.Attribute(modeAttribute);
|
|
mode = name.Value;
|
|
}
|
|
|
|
// and if we didn't find the above, fall back to client v server in xsf2:solutionDefinition
|
|
if (mode == null)
|
|
{
|
|
IEnumerable<XElement> allSolutionDefs = document.Descendants(xsf2Namespace + solutionDefinition);
|
|
foreach (XElement solutionDef in allSolutionDefs)
|
|
{
|
|
if (mode != null) throw new ArgumentException("Found more than one xsf2:solutionDefition!");
|
|
XElement extension = solutionDef.Element(xsf2Namespace + solutionPropertiesExtension);
|
|
if (extension != null && extension.Attribute(branchAttribute) != null && extension.Attribute(branchAttribute).Equals("contentType"))
|
|
{
|
|
mode = "Document Information Panel";
|
|
}
|
|
else
|
|
{
|
|
XAttribute compat = solutionDef.Attribute(runtimeCompatibilityAttribute);
|
|
mode = compat.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// and if we still found nothing, it's a 2003 form and must be client:
|
|
if (mode == null)
|
|
mode = "client";
|
|
|
|
m.ModeName = mode;
|
|
|
|
string compatibility = null;
|
|
foreach (XElement xDoc in document.Descendants(xsfNamespace + xDocumentClass))
|
|
{
|
|
if (compatibility != null) throw new ArgumentException("Multiple xDocumentClass nodes found!");
|
|
compatibility = xDoc.Attribute(solutionFormatVersionAttribute).Value;
|
|
}
|
|
m.Compatibility = compatibility;
|
|
|
|
yield return m;
|
|
yield break;
|
|
}
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + ModeName + " " + Compatibility;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return ModeName + "," + Compatibility;
|
|
}
|
|
}
|
|
|
|
//Product Version Class
|
|
|
|
class ProductVersion : InfoPathFeature
|
|
{
|
|
private const string productVersionAttribute = @"productVersion";
|
|
|
|
public string Version { get; private set; }
|
|
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
//ProductVersion is off the root node, so grab that value and return the value.
|
|
XAttribute attribute = document.Root.Attribute(productVersionAttribute);
|
|
|
|
if (attribute != null)
|
|
{
|
|
ProductVersion pv = new ProductVersion();
|
|
pv.Version = attribute.Value;
|
|
yield return pv;
|
|
}
|
|
|
|
// nothing left
|
|
yield break;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": " + Version;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return Version;
|
|
}
|
|
}
|
|
|
|
//Publish Class
|
|
|
|
class PublishUrl : InfoPathFeature
|
|
{
|
|
#region Private stuff
|
|
private const string baseUrl = @"baseUrl";
|
|
private const string relativeUrlBaseAttribute = @"relativeUrlBase";
|
|
private const string publishUrlAttribute = @"publishUrl";
|
|
private const string xDocumentClass = @"xDocumentClass";
|
|
|
|
private PublishUrl() { }
|
|
#endregion
|
|
|
|
#region Public interface
|
|
public string Publish { get; private set; }
|
|
public string RelativeBase { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Instead of logging on feature per control, I do 1 feature per control type along with the number of occurrences
|
|
/// </summary>
|
|
/// <param name="document"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<InfoPathFeature> ParseFeature(XDocument document)
|
|
{
|
|
PublishUrl pubRule = new PublishUrl();
|
|
IEnumerable<XElement> allElements = document.Descendants(xsf3Namespace + baseUrl);
|
|
// collect the control counts
|
|
foreach (XElement element in allElements)
|
|
{
|
|
if (pubRule.RelativeBase != null) throw new ArgumentException("Should only see one xsf3:baseUrl node");
|
|
XAttribute pathAttribute = element.Attribute(relativeUrlBaseAttribute);
|
|
if (pathAttribute != null) // this attribute is technically optional per xsf3 spec
|
|
{
|
|
pubRule.RelativeBase = pathAttribute.Value;
|
|
}
|
|
}
|
|
|
|
allElements = document.Descendants(xsfNamespace + xDocumentClass);
|
|
foreach (XElement element in allElements)
|
|
{
|
|
if (pubRule.Publish != null) throw new ArgumentException("Should only see one xsf:xDocumentClass node");
|
|
XAttribute pubUrl = element.Attribute(publishUrlAttribute);
|
|
if (pubUrl != null)
|
|
{
|
|
pubRule.Publish = pubUrl.Value;
|
|
}
|
|
}
|
|
|
|
yield return pubRule;
|
|
// nothing left
|
|
yield break;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return FeatureName + ": RelativeBase=" + RelativeBase + ", PublishUrl=" + Publish;
|
|
}
|
|
|
|
public override string ToCSV()
|
|
{
|
|
return RelativeBase + "," + Publish;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
// InfoPath File Class
|
|
|
|
public abstract class InfoPathFile
|
|
{
|
|
private XDocument _xDocument;
|
|
private List<InfoPathFeature> _features;
|
|
|
|
#region Public interface
|
|
public CabFileInfo CabFileInfo { get; protected set; }
|
|
public XDocument XDocument { get { InitializeXDocument(); return _xDocument; } }
|
|
|
|
/// <summary>
|
|
/// Enumerates the features found in this InfoPathFile.
|
|
/// </summary>
|
|
public IEnumerable<InfoPathFeature> Features
|
|
{
|
|
get
|
|
{
|
|
InitializeFeatures();
|
|
foreach (InfoPathFeature feature in _features)
|
|
yield return feature;
|
|
yield break;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Abstract methods
|
|
protected abstract IEnumerable<Func<XDocument, IEnumerable<InfoPathFeature>>> FeatureDiscoverers { get; }
|
|
#endregion
|
|
|
|
#region Private helpers
|
|
/// <summary>
|
|
/// Loads the XDocument that is the file from the cab. All parseable InfoPath files are xml documents.
|
|
/// </summary>
|
|
private void InitializeXDocument()
|
|
{
|
|
try
|
|
{
|
|
if (_xDocument != null) return;
|
|
|
|
Stream content = CabFileInfo.OpenRead();
|
|
// use the 3.5compatible Load API that takes an XmlReader, that way this will work when targeting either .NET3.5 or later
|
|
System.Xml.XmlReader reader = System.Xml.XmlReader.Create(content);
|
|
_xDocument = XDocument.Load(reader);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run all the FeatureDiscoverers for this file type. Each deriving InfoPathFile type
|
|
/// defines what features it *might* contain.
|
|
/// </summary>
|
|
private void InitializeFeatures()
|
|
{
|
|
try
|
|
{
|
|
if (_features != null) return;
|
|
_features = new List<InfoPathFeature>();
|
|
foreach (Func<XDocument, IEnumerable<InfoPathFeature>> discoverer in FeatureDiscoverers)
|
|
foreach (InfoPathFeature feature in discoverer.Invoke(XDocument))
|
|
_features.Add(feature);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.Message);
|
|
Logging.GetInstance().WriteToLogFile(Logging.Error, ex.StackTrace);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
// InfoPath Manifestv Class
|
|
|
|
public class InfoPathManifest : InfoPathFile
|
|
{
|
|
#region Private members
|
|
private static XNamespace xsfNamespace = @"http://schemas.microsoft.com/office/infopath/2003/solutionDefinition";
|
|
private static XNamespace xsf2Namespace = @"http://schemas.microsoft.com/office/infopath/2006/solutionDefinition/extensions";
|
|
private static XNamespace xsf3Namespace = @"http://schemas.microsoft.com/office/infopath/2009/solutionDefinition/extensions";
|
|
private const string viewNode = @"mainpane";
|
|
private const string viewNameAttribute = @"transform";
|
|
private const string xDocumentClass = @"xDocumentClass";
|
|
private const string uniqueUrn = @"name";
|
|
|
|
private XElement _xDocumentNode;
|
|
private List<string> _viewNames;
|
|
private InfoPathManifest() { }
|
|
#endregion
|
|
|
|
#region Public stuff
|
|
public List<string> ViewNames { get { InitializeViewNames(); return _viewNames; } }
|
|
public string Name
|
|
{
|
|
get
|
|
{
|
|
InitializeXDocumentNode();
|
|
|
|
//InfoPath 2003 forms don't have a Name attribute, so return an empty string instead of throwing an exception.
|
|
return (_xDocumentNode.Attribute(uniqueUrn) == null) ? string.Empty : _xDocumentNode.Attribute(uniqueUrn).Value;
|
|
}
|
|
}
|
|
public static InfoPathManifest Create(CabFileInfo cabFileInfo)
|
|
{
|
|
InfoPathManifest manifest = new InfoPathManifest();
|
|
manifest.CabFileInfo = cabFileInfo;
|
|
return manifest;
|
|
}
|
|
#endregion
|
|
|
|
#region Override implementations
|
|
protected override IEnumerable<Func<XDocument, IEnumerable<InfoPathFeature>>> FeatureDiscoverers
|
|
{
|
|
get
|
|
{
|
|
yield return Mode.ParseFeature;
|
|
yield return PublishUrl.ParseFeature;
|
|
yield return DataConnection.ParseFeature;
|
|
yield return ManagedCode.ParseFeature;
|
|
yield return ProductVersion.ParseFeature;
|
|
yield return DataRule.ParseFeature;
|
|
yield return DataValidation.ParseFeature;
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private helpers
|
|
/// <summary>
|
|
/// This is the root node of the manifest.xsf file from which get a few interesting properties.
|
|
/// </summary>
|
|
private void InitializeXDocumentNode()
|
|
{
|
|
if (_xDocumentNode != null) return;
|
|
IEnumerable<XElement> elements = XDocument.Descendants(xsfNamespace + xDocumentClass);
|
|
foreach (XElement element in elements)
|
|
{
|
|
if (_xDocumentNode != null) throw new ArgumentException("Manifest has multiple xDocumentClass nodes");
|
|
_xDocumentNode = element;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An xsn can have many resource files in it. The only reliable way to know which ones are views is to
|
|
/// parse the manifest for those that are called out as such. We just need string names of them because
|
|
/// the Microsoft.Deployment.Compression.Cab code will happily find them by name.
|
|
/// </summary>
|
|
private void InitializeViewNames()
|
|
{
|
|
if (_viewNames != null) return;
|
|
_viewNames = new List<string>();
|
|
|
|
foreach (XElement mainpane in XDocument.Descendants(xsfNamespace + viewNode))
|
|
{
|
|
_viewNames.Add(mainpane.Attribute(viewNameAttribute).Value);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
}
|
|
|
|
//InfoPath Template Class
|
|
|
|
|
|
public class InfoPathTemplate
|
|
{
|
|
#region Members and Basics
|
|
public CabInfo CabInfo { get; private set; }
|
|
public List<InfoPathView> _infoPathViews;
|
|
private InfoPathManifest _infoPathManifest;
|
|
|
|
public static InfoPathTemplate CreateTemplate(string path)
|
|
{
|
|
// Lazy Init
|
|
CabInfo cabInfo = new CabInfo(path);
|
|
InfoPathTemplate template = new InfoPathTemplate();
|
|
template.CabInfo = cabInfo;
|
|
return template;
|
|
}
|
|
#endregion
|
|
|
|
|
|
#region Various Properties we should support
|
|
public List<InfoPathView> InfoPathViews { get { InitializeViews(); return _infoPathViews; } }
|
|
public InfoPathManifest InfoPathManifest { get { InitializeManifest(); return _infoPathManifest; } }
|
|
public IEnumerable<InfoPathFile> FeaturedFiles { get { yield return InfoPathManifest; foreach (InfoPathView view in InfoPathViews) yield return view; yield break; } }
|
|
public IEnumerable<InfoPathFeature> Features { get { foreach (InfoPathFile file in FeaturedFiles) { foreach (InfoPathFeature feature in file.Features) yield return feature; } yield break; } }
|
|
#endregion
|
|
|
|
#region Private helpers to compute things
|
|
private void InitializeManifest()
|
|
{
|
|
if (_infoPathManifest != null) return;
|
|
|
|
// get the files named manifest.xsf (there should be one)
|
|
IList<CabFileInfo> cbInfos = null;
|
|
try
|
|
{
|
|
cbInfos = CabInfo.GetFiles("manifest.xsf");
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
Console.WriteLine(error.Message.ToString());
|
|
}
|
|
|
|
// TODO check for corrupt infopath forms
|
|
if (cbInfos == null)
|
|
throw new ArgumentException("Invalid InfoPath xsn");
|
|
if (cbInfos.Count != 1) throw new ArgumentException("Invalid InfoPath xsn");
|
|
_infoPathManifest = InfoPathManifest.Create(cbInfos[0]);
|
|
}
|
|
|
|
private void InitializeViews()
|
|
{
|
|
if (_infoPathViews != null) return;
|
|
_infoPathViews = new List<InfoPathView>();
|
|
|
|
foreach (string name in InfoPathManifest.ViewNames)
|
|
{
|
|
IList<CabFileInfo> cbInfos = CabInfo.GetFiles(name);
|
|
if (cbInfos.Count != 1) throw new ArgumentException(String.Format("Malformed template file: view {0} not found", name));
|
|
InfoPathView viewFile = InfoPathView.Create(cbInfos[0]);
|
|
_infoPathViews.Add(viewFile);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
//InfoPath View Class
|
|
|
|
/// <summary>
|
|
/// Selfexplanatory implementation
|
|
/// </summary>
|
|
public class InfoPathView : InfoPathFile
|
|
{
|
|
#region Private stuff
|
|
private InfoPathView() { }
|
|
#endregion
|
|
|
|
#region Public stuff
|
|
public static InfoPathView Create(CabFileInfo cabFileInfo)
|
|
{
|
|
InfoPathView view = new InfoPathView();
|
|
view.CabFileInfo = cabFileInfo;
|
|
return view;
|
|
}
|
|
#endregion
|
|
|
|
#region Override implementations
|
|
protected override IEnumerable<Func<XDocument, IEnumerable<InfoPathFeature>>> FeatureDiscoverers
|
|
{
|
|
get
|
|
{
|
|
yield return Control.ParseFeature;
|
|
yield return FormattingRule.ParseFeature;
|
|
yield break;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
}
|
|
|
|
public class BucketCounter
|
|
{
|
|
private Dictionary<string, Int32> _dictionary;
|
|
|
|
public BucketCounter()
|
|
{
|
|
_dictionary = new Dictionary<string, int>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a key and initializes it to a count of zero.
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
public void DefineKey(string key)
|
|
{
|
|
if (!_dictionary.ContainsKey(key))
|
|
_dictionary.Add(key, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make sure we have a counter for the key and increment it.
|
|
/// Note that this is why I use Int32 objects instead of int
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
public void IncrementKey(string key)
|
|
{
|
|
DefineKey(key);
|
|
_dictionary[key]++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// For each KeyValuePair, return it. I create new ones here so that
|
|
/// a caller can't accidentally mess up the values in the Dictionary
|
|
/// The buckets are returned in sortedbykey order because that's more useful than a random order
|
|
// </summary>
|
|
public IEnumerable<KeyValuePair<string, int>> Buckets
|
|
{
|
|
get
|
|
{
|
|
List<string> orderedKeys = new List<string>();
|
|
foreach (string key in _dictionary.Keys)
|
|
orderedKeys.Add(key);
|
|
orderedKeys.Sort();
|
|
|
|
foreach (string key in orderedKeys)
|
|
yield return new KeyValuePair<string, int>(key, _dictionary[key]);
|
|
yield break;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|