Google Adwords API C# Client Library - c#

I downloaded the Adwords API client library for DOTNET. I am trying to use the GetAccountHierarchy.cs file example to get the account client list from Adwords Account. The code is appended as below:
using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201209;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Google.Api.Ads.AdWords.Examples.CSharp.v201209 {
/// <summary>
/// This code example illustrates how to retrieve the account hierarchy under
/// an account. This code example won't work with Test Accounts. See
/// https://developers.google.com/adwords/api/docs/test-accounts
///
/// Tags: ManagedCustomerService.get
/// </summary>
public class GetAccountHierarchy : ExampleBase {
/// <summary>
/// Main method, to run this code example as a standalone application.
/// </summary>
/// <param name="args">The command line arguments.</param>
public static void Main(string[] args) {
GetAccountHierarchy codeExample = new GetAccountHierarchy();
Console.WriteLine(codeExample.Description);
try {
codeExample.Run(new AdWordsUser());
} catch (Exception ex) {
Console.WriteLine("An exception occurred while running this code example. {0}",
ExampleUtilities.FormatException(ex));
}
}
/// <summary>
/// Returns a description about the code example.
/// </summary>
public override string Description {
get {
return "This code example illustrates how to retrieve the account hierarchy under" +
" an account. This code example won't work with Test Accounts. See " +
"https://developers.google.com/adwords/api/docs/test-accounts";
}
}
/// <summary>
/// Runs the code example.
/// </summary>
/// <param name="user">The AdWords user.</param>
public void Run(AdWordsUser user) {
// Get the ManagedCustomerService.
ManagedCustomerService managedCustomerService = (ManagedCustomerService) user.GetService(
AdWordsService.v201209.ManagedCustomerService);
managedCustomerService.RequestHeader.clientCustomerId = null;
// Create selector.
Selector selector = new Selector();
selector.fields = new String[] {"Login", "CustomerId", "Name"};
try {
// Get results.
ManagedCustomerPage page = managedCustomerService.get(selector);
// Display serviced account graph.
if (page.entries != null) {
// Create map from customerId to customer node.
Dictionary<long, ManagedCustomerTreeNode> customerIdToCustomerNode =
new Dictionary<long, ManagedCustomerTreeNode>();
// Create account tree nodes for each customer.
foreach (ManagedCustomer customer in page.entries) {
ManagedCustomerTreeNode node = new ManagedCustomerTreeNode();
node.Account = customer;
customerIdToCustomerNode.Add(customer.customerId, node);
}
// For each link, connect nodes in tree.
if (page.links != null) {
foreach (ManagedCustomerLink link in page.links) {
ManagedCustomerTreeNode managerNode =
customerIdToCustomerNode[link.managerCustomerId];
ManagedCustomerTreeNode childNode = customerIdToCustomerNode[link.clientCustomerId];
childNode.ParentNode = managerNode;
if (managerNode != null) {
managerNode.ChildAccounts.Add(childNode);
}
}
}
// Find the root account node in the tree.
ManagedCustomerTreeNode rootNode = null;
foreach (ManagedCustomer account in page.entries) {
if (customerIdToCustomerNode[account.customerId].ParentNode == null) {
rootNode = customerIdToCustomerNode[account.customerId];
break;
}
}
// Display account tree.
Console.WriteLine("Login, CustomerId, Name");
Console.WriteLine(rootNode.ToTreeString(0, new StringBuilder()));
} else {
Console.WriteLine("No serviced accounts were found.");
}
} catch (Exception ex) {
throw new System.ApplicationException("Failed to create ad groups.", ex);
}
}
/**
* Example implementation of a node that would exist in an account tree.
*/
class ManagedCustomerTreeNode {
/// <summary>
/// The parent node.
/// </summary>
private ManagedCustomerTreeNode parentNode;
/// <summary>
/// The account associated with this node.
/// </summary>
private ManagedCustomer account;
/// <summary>
/// The list of child accounts.
/// </summary>
private List<ManagedCustomerTreeNode> childAccounts = new List<ManagedCustomerTreeNode>();
/// <summary>
/// Gets or sets the parent node.
/// </summary>
public ManagedCustomerTreeNode ParentNode {
get { return parentNode; }
set { parentNode = value; }
}
/// <summary>
/// Gets or sets the account.
/// </summary>
public ManagedCustomer Account {
get { return account; }
set { account = value; }
}
/// <summary>
/// Gets the child accounts.
/// </summary>
public List<ManagedCustomerTreeNode> ChildAccounts {
get { return childAccounts; }
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override String ToString() {
String login = String.IsNullOrEmpty(account.login) ? "(no login)" : account.login;
return String.Format("{0}, {1}, {2}", login, account.customerId, account.name);
}
/// <summary>
/// Returns a string representation of the current level of the tree and
/// recursively returns the string representation of the levels below it.
/// </summary>
/// <param name="depth">The depth of the node.</param>
/// <param name="sb">The String Builder containing the tree
/// representation.</param>
/// <returns>The tree string representation.</returns>
public StringBuilder ToTreeString(int depth, StringBuilder sb) {
sb.Append(new String('-', depth * 2));
sb.Append(this);
sb.Append("\n");
foreach (ManagedCustomerTreeNode childAccount in childAccounts) {
childAccount.ToTreeString(depth + 1, sb);
}
return sb;
}
}
}
}
I am trying to figure out where i can pass the parameter like my account username, password, developer token and access token to this function? Does anybody know how i can pass those info to this code? Thanks.

It seems to me, wildly guessing, the account, username and password are present in some secondary resource files not passed in as arguments, or typed into the program as input, but loaded automatically from some 'special' location by the Google API.
Do you, perhaps, have a PGP type keys downloaded from the AdWords program?

I know this getting old but you can pass parameter in your app.config/web.config
<add key="ClientCustomerId" value="insert your client customer id"/>
<add key="AuthorizationMethod" value="OAuth2"/>
<add key="OAuth2ClientId" value="insert oauth2 client id"/>
<add key="OAuth2ClientSecret" value="insert oauth client scret"/>
<add key="OAuth2RefreshToken" value="insert oauth2 refresh token"/>
all oauth2 value you can get from google API Manager

Related

Read XML node using reflection c#

I have this function to read xml values and create an instance of a class.
Is there a way to combine linq and reflection to not specify the properties of the class and create the class with less code lines?
I try to avoid to modify this method if I add or remove some fields to Test_Class
/// <summary>
/// Get the values of a xml node and return a class of type Class_Test
/// </summary>
/// <param name="sXmlFileName">Path to xml file.</param>
/// <param name="sNodeName">Name of node to get values.</param>
/// <returns>Class_Test New class with the values set from XML.</returns>
public static Class_Test Func_ReadXMLNode(string sXmlFileName, string sNodeName)
{
//Load XML
XDocument xml_Document;
Class_Test result;
try
{
xml_Document = XDocument.Load(sXmlFileName);
//Read XML Section
var xmlValues = from r in xml_Document.Descendants(sNodeName)
select new Class_Test
{
sNetworkInterfaceName = Convert.ToString(r.Element("").Value),
iNetworkInterfacePort = Convert.ToInt32(r.Element("").Value),
sCertificateFile = Convert.ToString(r.Element("").Value),
sCertificateName = Convert.ToString(r.Element("").Value),
iKeepAliveSendingTime = Convert.ToInt32(r.Element("").Value),
iMaximumTimeWithoutKeepAlive = Convert.ToInt32(r.Element("").Value),
sSqlServer = Convert.ToString(r.Element("").Value),
sDatabase = Convert.ToString(r.Element("").Value),
iFtpRetries = Convert.ToInt32(r.Element("").Value),
sDetectionFilesDirectory = Convert.ToString(r.Element("").Value),
sImgDirectory = Convert.ToString(r.Element("").Value),
sLocalDirectory = Convert.ToString(r.Element("").Value),
sOffenceDirectory = Convert.ToString(r.Element("").Value),
sTmpDirectory = Convert.ToString(r.Element("").Value)
};
result = xmlValues.FirstOrDefault();
}
catch (IOException Exception1)
{
LogHelper.Func_WriteEventInLogFile(DateTime.Now.ToLocalTime(), enum_EventTypes.Error, "Func_ReadXMLConfig()", "Source = " + Exception1.Source.Replace("'", "''") + ", Message = " + Exception1.Message.Replace("'", "''"));
result = new Class_Test();
}
return result;
}
And this is Class_Text:
/// <summary>
/// Class of operation mode.
/// </summary>
public class Class_OperationMode
{
#region CONFIG_PARAMETERS
/// <summary>
/// Name of the network interface.
/// </summary>
public string sNetworkInterfaceName;
/// <summary>
/// Port of the network interface.
/// </summary>
public int iNetworkInterfacePort;
/// <summary>
/// Path to certificate file.
/// </summary>
public string sCertificateFile;
/// <summary>
/// Name of the certificate.
/// </summary>
public string sCertificateName;
/// <summary>
/// Time to keep alive the connection while sending data.
/// </summary>
public int iKeepAliveSendingTime;
/// <summary>
/// Time before timeout of the connection.
/// </summary>
public int iMaximumTimeWithoutKeepAlive;
/// <summary>
/// Database server instance.
/// </summary>
public string sSqlServer;
/// <summary>
/// Path to .mdf file of database.
/// </summary>
public string sDatabase;
/// <summary>
/// Max retries to try to connect to FTP Server.
/// </summary>
public int iFtpRetries;
/// <summary>
/// Path to detections files directory.
/// </summary>
public string sDetectionFilesDirectory;
/// <summary>
/// Path to images directory.
/// </summary>
public string sImgDirectory;
/// <summary>
/// Path to local directory.
/// </summary>
public string sLocalDirectory;
/// <summary>
/// Path to folder where save and retrieve offences.
/// </summary>
public string sOffenceDirectory;
/// <summary>
/// Path to temp directory.
/// </summary>
public string sTmpDirectory;
#endregion
UPDATE: Thanks to comments, I updated code to:
public static Class_Test Func_ReadXMLNode(string sXmlFileName, string sNodeName){
//Load XML
XDocument xml_Document;
Class_Test result;
try
{
xml_Document = XDocument.Load(sXmlFileName);
//Read XML Section
//Get xml values of descendants
XElement xmlValues = xml_Document.Descendants(sNodeName).FirstOrDefault();
//Create serializer
XmlSerializer serializer = new XmlSerializer(typeof(Class_Test));
//Deserialize
using (XmlReader reader = xmlValues.CreateReader())
{
result = (Class_Test)serializer.Deserialize(reader);
}
}
catch (IOException Exception1)
{
LogHelper.Func_WriteEventInLogFile(DateTime.Now.ToLocalTime(), enum_EventTypes.Error, "Func_ReadXMLConfig()", "Source = " + Exception1.Source.Replace("'", "''") + ", Message = " + Exception1.Message.Replace("'", "''"));
result = new Class_Test();
}
return result;
}
Thanks in advance. Best regards,
JoaquĆ­n
I think what youre looking for is serialization/deserialization. Theres lots of useful stuff in .net for handling xml serialization. Ive stolen this example from the docs (link underneath).
XmlSerializer serializer = new
XmlSerializer(typeof(OrderedItem));
// A FileStream is needed to read the XML document.
FileStream fs = new FileStream(filename, FileMode.Open);
XmlReader reader = XmlReader.Create(fs);
// Declare an object variable of the type to be deserialized.
OrderedItem i;
// Use the Deserialize method to restore the object's state.
i = (OrderedItem)serializer.Deserialize(reader);
fs.Close();
From https://msdn.microsoft.com/en-us/library/tz8csy73(v=vs.110).aspx

C# Modifying multiple Dictionary Values throws error

I'm using HTML Agility Pack to download some pages, I'm also storing cookie info and login info from a form. The issue I'm having is that I'm doing a GET operation to get the cookie info and form details so on the next step it can login using a username and password, I'm then trying to set the value of the username and password input fields so that I can then post to a login page. The input fields are being stored in a Dictionary<string,string>. I'm able to change one of the dictionary values, but after that I get the error:
"Object reference not set to an instance of an object.".
If I try to change password and then username the username throws the error and vice-versa. This is the code im using:
class Main
{
private void testLogin()
{
BrowserSession b = new BrowserSession();
b.Get("login.html");
b.FormElements["email_address"] = #"email";
b.FormElements["password"] = "password";
string response = b.Post("index.php?main_page=login");
}
}
}
public class BrowserSession
{
private bool _isPost;
private HtmlDocument _htmlDoc;
/// <summary>
/// System.Net.CookieCollection. Provides a collection container for instances of Cookie class
/// </summary>
public CookieCollection Cookies { get; set; }
/// <summary>
/// Provide a key-value-pair collection of form elements
/// </summary>
public FormElementCollection FormElements { get; set; }
/// <summary>
/// Makes a HTTP GET request to the given URL
/// </summary>
public string Get(string url)
{
_isPost = false;
CreateWebRequestObject().Load(url);
return _htmlDoc.DocumentNode.InnerHtml;
}
/// <summary>
/// Makes a HTTP POST request to the given URL
/// </summary>
public string Post(string url)
{
_isPost = true;
CreateWebRequestObject().Load(url, "POST");
return _htmlDoc.DocumentNode.InnerHtml;
}
/// <summary>
/// Creates the HtmlWeb object and initializes all event handlers.
/// </summary>
private HtmlWeb CreateWebRequestObject()
{
HtmlWeb web = new HtmlWeb();
web.UseCookies = true;
web.PreRequest = new HtmlWeb.PreRequestHandler(OnPreRequest);
web.PostResponse = new HtmlWeb.PostResponseHandler(OnAfterResponse);
web.PreHandleDocument = new HtmlWeb.PreHandleDocumentHandler(OnPreHandleDocument);
return web;
}
/// <summary>
/// Event handler for HtmlWeb.PreRequestHandler. Occurs before an HTTP request is executed.
/// </summary>
protected bool OnPreRequest(HttpWebRequest request)
{
AddCookiesTo(request); // Add cookies that were saved from previous requests
if (_isPost) AddPostDataTo(request); // We only need to add post data on a POST request
return true;
}
/// <summary>
/// Event handler for HtmlWeb.PostResponseHandler. Occurs after a HTTP response is received
/// </summary>
protected void OnAfterResponse(HttpWebRequest request, HttpWebResponse response)
{
SaveCookiesFrom(response); // Save cookies for subsequent requests
}
/// <summary>
/// Event handler for HtmlWeb.PreHandleDocumentHandler. Occurs before a HTML document is handled
/// </summary>
protected void OnPreHandleDocument(HtmlDocument document)
{
SaveHtmlDocument(document);
}
/// <summary>
/// Assembles the Post data and attaches to the request object
/// </summary>
private void AddPostDataTo(HttpWebRequest request)
{
string payload = FormElements.AssemblePostPayload();
byte[] buff = Encoding.UTF8.GetBytes(payload.ToCharArray());
request.ContentLength = buff.Length;
request.ContentType = "application/x-www-form-urlencoded";
System.IO.Stream reqStream = request.GetRequestStream();
reqStream.Write(buff, 0, buff.Length);
}
/// <summary>
/// Add cookies to the request object
/// </summary>
private void AddCookiesTo(HttpWebRequest request)
{
if (Cookies != null && Cookies.Count > 0)
{
request.CookieContainer.Add(Cookies);
}
}
/// <summary>
/// Saves cookies from the response object to the local CookieCollection object
/// </summary>
private void SaveCookiesFrom(HttpWebResponse response)
{
if (response.Cookies.Count > 0)
{
if (Cookies == null) Cookies = new CookieCollection();
Cookies.Add(response.Cookies);
}
}
/// <summary>
/// Saves the form elements collection by parsing the HTML document
/// </summary>
private void SaveHtmlDocument(HtmlDocument document)
{
_htmlDoc = document;
FormElements = new FormElementCollection(_htmlDoc);
}
}
public class FormElementCollection : Dictionary<string, string>
{
/// <summary>
/// Constructor. Parses the HtmlDocument to get all form input elements.
/// </summary>
public FormElementCollection(HtmlDocument htmlDoc)
{
var inputs = htmlDoc.DocumentNode.SelectSingleNode("//div[#id = 'loginDefault']").Descendants("input");
foreach (var element in inputs)
{
string name = element.GetAttributeValue("name", "undefined");
string value = element.GetAttributeValue("value", "");
if (!name.Equals("undefined")) { Add(name, value); }
}
}
/// <summary>
/// Assembles all form elements and values to POST. Also html encodes the values.
/// </summary>
public string AssemblePostPayload()
{
StringBuilder sb = new StringBuilder();
foreach (var element in this)
{
string value = HttpUtility.UrlEncode(element.Value);
sb.Append("&" + element.Key + "=" + value);
}
return sb.ToString().Substring(1);
}
}
Any help would be much appreciated.
While debugging put a break point after calling the b constractor.
You ll find the FormElements property is Null.
You need to inialize it in BrowserSession constractor.

The best overloaded method match for has some invalid arguments

I am using VS 2010 C#. My code is as follows:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using MarketplaceWebServiceOrders;
using MarketplaceWebServiceOrders.Model;
namespace FetchNewOrdersJob
{
public class MarketplaceWebServiceOrders
{
private volatile bool isRunning;
private OrderFetcher orderFetcher;
private TimeSpan _checkOrdersInterval = TimeSpan.FromMinutes(15.0);
/// <summary>
/// Gets or sets the order check interval. Defaults to 15 minutes.
/// </summary>
public TimeSpan CheckOrdersInterval
{
get { return _checkOrdersInterval; }
set { _checkOrdersInterval = value; }
}
/// <summary>
/// Internal method to handle an order.
/// </summary>
protected virtual void HandleOrder(Order order)
{
Console.WriteLine("Processing Order:");
Console.WriteLine("---------------------------------------------------");
Console.WriteLine(order.ToString());
// Fetch the order items in each order
orderFetcher.FetchOrderItems(order.AmazonOrderId, delegate(OrderItem item)
{
Console.WriteLine("\tProcessing Order Item");
Console.WriteLine("\t---------------------------------------------------"); // Process order item here.
Console.WriteLine("\t" + item.ToString().Replace("\n", "\n\t"));
});
Console.WriteLine("=================================================");
Console.WriteLine();
}
/// <summary>
/// Method to continuously check orders over an interval, and list OrderItems for those Orders.
/// </summary>
private void OrdersJobThread(object obj)
{
orderFetcher.ProcessOrder += HandleOrder;
if (this.CheckOrdersInterval == TimeSpan.MinValue)
{
throw new ArgumentException("The CheckOrdersInterval TimeSpan cannot be zero.", "CheckOrdersInterval");
}
DateTime startCheckInterval = DateTime.Now.Subtract(CheckOrdersInterval);
// Continue forever until the isRunning flag is cleared.
while (isRunning)
{
try
{
// Check the orders for this interval.
DateTime checkInterval = startCheckInterval;
startCheckInterval = DateTime.Now.Subtract(TimeSpan.FromMinutes(3.0));
Console.WriteLine("Fetching orders from " + checkInterval.ToString() + " to " + startCheckInterval.ToString());
orderFetcher.FetchOrders(checkInterval, startCheckInterval);
// Wait for the next interval.
Console.WriteLine("Fetch complete. Sleeping until next interval.");
while (isRunning && DateTime.Now.Subtract(startCheckInterval) < CheckOrdersInterval)
{
Thread.Sleep(1000);
}
}
catch(Exception err)
{
Console.WriteLine("Error: " + err.Message + ". Orders job thread is exiting.");
isRunning = false;
}
}
}
/// <summary>
/// Sample code to invoke the OrderFetcher.
/// </summary>
/// <param name="service">MarketplaceWebServiceOrders object.</param>
/// <param name="sellerId">The seller Id.</param>
/// <param name="marketplaceIdList">List of marketplaces passed in to the GetOrders call.</param>
public static void InvokeOrderFetcherSample(
MarketplaceWebServiceOrders service,
string sellerId,
string [] marketplaceIdList
)
{
// Create a FetchOrderUpdates job with the default time span.
MarketplaceWebServiceOrders job = new MarketplaceWebServiceOrders();
job.isRunning = true;
job.orderFetcher = new OrderFetcher(service, sellerId, marketplaceIdList);
Thread jobThread = new Thread(job.OrdersJobThread);
jobThread.IsBackground = true;
jobThread.Start();
// Pause on the main thread for one hour or until the thread exits, then end the job.
jobThread.Join(1000 * 60 * 60);
job.isRunning = false;
// Block until the thread terminates to prevent any requests in progress from being aborted.
while (jobThread.IsAlive)
{
Thread.Sleep(1000);
}
}
}
}
I get two errors and can't seem to figure this out
Argument 1: cannot convert from 'FetchNewOrdersJob.MarketplaceWebServiceOrders' to 'MarketplaceWebServiceOrders.MarketplaceWebServiceOrders'
The best overloaded method match for 'MarketplaceWebServiceOrders.OrderFetcher.OrderFetcher(MarketplaceWebServiceOrders.MarketplaceWebServiceOrders, string, string[])' has some invalid arguments
The Line number of the error is 112: job.orderFetcher = new OrderFetcher(service, sellerId, marketplaceIdList);
OrderFetcher.cs
using System;
using System.Collections.Generic;
using System.Text;
using MarketplaceWebServiceOrders.Model;
namespace MarketplaceWebServiceOrders
{
/// <summary>
/// Sample helper class to Fetch Orders and OrderItems using the Amazon MWS Orders API.
/// </summary>
public class OrderFetcher
{
public delegate void RetriableMethodCall();
public delegate void ProcessOrderHandler(Order order);
public delegate void ProcessOrderItemHandler(OrderItem orderItem);
/// <summary>
/// Default amount of time, in milliseconds, to sleep if a request is throttled; default to 1 per 10 minutes.
/// </summary>
public const int DEFAULT_THROTTLED_WAIT_TIMEOUT = 10 * 60 * 1000;
/// <summary>
/// Default throttling limit for ListOrders calls; default to 1 per 12 seconds.
/// </summary>
private const int LIST_ORDERS_DEFAULT_THROTTLE_LIMIT = 12 * 1000;
/// <summary>
/// Default throttling limit for ListOrderItems calls; default to 1 per 100 minutes.
/// </summary>
private const int LIST_ORDER_ITEMS_DEFAULT_THROTTLE_LIMIT = 10 * 60 * 1000;
private MarketplaceWebServiceOrders mwsService;
private string mwsSellerId;
private string[] mwsMarketplaceIdList;
private DateTime lastServiceCall = DateTime.MinValue;
private ProcessOrderHandler _processOrder;
/// <summary>
/// Event called when an order is received for processing.
/// </summary>
public event ProcessOrderHandler ProcessOrder
{
add { _processOrder += value; }
remove { _processOrder -= value; }
}
/// <summary>
/// Creates a new instance of the OrderFetcherSample class.
/// </summary>
/// <param name="service"></param>
public OrderFetcher(MarketplaceWebServiceOrders service, string sellerId, string[] marketplaceIdList)
{
mwsService = service;
mwsSellerId = sellerId;
mwsMarketplaceIdList = marketplaceIdList;
}
/// <summary>
/// Fetches all orders created between the starting time and the server's
/// local system time minus two minutes.
/// <param name="startTime">The starting time period of orders to fetch.</param>
public void FetchOrders(DateTime startTime)
{
FetchOrders(startTime, DateTime.MinValue);
}
/// <summary>
/// Fetches all orders created in the given time period and processes them locally.
/// <param name="startTime">The starting time period of orders to fetch.</param>
/// <param name="endTime">The ending time period of orders to fetch.</param>
public void FetchOrders(DateTime startTime, DateTime endTime)
{
ListOrdersRequest request = new ListOrdersRequest();
request.CreatedAfter = startTime;
if (endTime != DateTime.MinValue)
{
request.CreatedBefore = endTime;
}
request.SellerId = mwsSellerId;
request.MarketplaceId = new MarketplaceIdList();
request.MarketplaceId.Id = new List<string>();
foreach (string marketplaceId in mwsMarketplaceIdList)
{
request.MarketplaceId.Id.Add(marketplaceId);
}
List<Order> orderList = new List<Order>();
ListOrdersResponse response = null;
OrderFetcher.InvokeRetriable(LIST_ORDERS_DEFAULT_THROTTLE_LIMIT, delegate()
{
response = mwsService.ListOrders(request);
ProcessOrders(response.ListOrdersResult.Orders.Order);
});
String nextTokenString = response.ListOrdersResult.NextToken;
while (!string.IsNullOrEmpty(nextTokenString))
{
// If NextToken is set, continue looping through the orders.
ListOrdersByNextTokenRequest nextRequest = new ListOrdersByNextTokenRequest();
nextRequest.NextToken = nextTokenString;
nextRequest.SellerId = mwsSellerId;
ListOrdersByNextTokenResponse nextResponse = null;
OrderFetcher.InvokeRetriable(LIST_ORDERS_DEFAULT_THROTTLE_LIMIT, delegate()
{
nextResponse = mwsService.ListOrdersByNextToken(nextRequest);
ProcessOrders(nextResponse.ListOrdersByNextTokenResult.Orders.Order);
});
nextTokenString = nextResponse.ListOrdersByNextTokenResult.NextToken;
}
}
/// <summary>
/// Method called by the FetchOrders method to process the orders.
/// </summary>
/// <param name="orders">List of orders returned by FetchOrders</param>
protected virtual void ProcessOrders(List<Order> orders)
{
foreach (Order order in orders)
{
if (_processOrder != null)
{
_processOrder(order);
}
}
}
/// <summary>
/// Fetches the OrderItems for the specified orderId.
/// </summary>
public void FetchOrderItems(string orderId, ProcessOrderItemHandler handler)
{
if (handler == null) throw new ArgumentNullException("handler");
ListOrderItemsRequest request = new ListOrderItemsRequest();
request.SellerId = mwsSellerId;
request.AmazonOrderId = orderId;
ListOrderItemsResponse response = null;
OrderFetcher.InvokeRetriable(LIST_ORDER_ITEMS_DEFAULT_THROTTLE_LIMIT, delegate()
{
response = mwsService.ListOrderItems(request);
foreach (OrderItem orderItem in response.ListOrderItemsResult.OrderItems.OrderItem)
{
handler(orderItem);
}
});
String nextTokenString = response.ListOrderItemsResult.NextToken;
while (!string.IsNullOrEmpty(nextTokenString))
{
// If NextToken is set, continue looping through the orders.
ListOrderItemsByNextTokenRequest nextRequest = new ListOrderItemsByNextTokenRequest();
nextRequest.NextToken = nextTokenString;
nextRequest.SellerId = mwsSellerId;
ListOrderItemsByNextTokenResponse nextResponse = null;
OrderFetcher.InvokeRetriable(LIST_ORDER_ITEMS_DEFAULT_THROTTLE_LIMIT, delegate()
{
nextResponse = mwsService.ListOrderItemsByNextToken(nextRequest);
foreach (OrderItem orderItem in nextResponse.ListOrderItemsByNextTokenResult.OrderItems.OrderItem)
{
handler(orderItem);
}
});
nextTokenString = nextResponse.ListOrderItemsByNextTokenResult.NextToken;
}
}
/// <summary>
/// Invokes a method in a retriable fashion.
/// </summary>
/// <param name="throttledWaitTime">The amount of time to wait if the request is throttled.</param>
/// <param name="method">The method to invoke.</param>
public static void InvokeRetriable(RetriableMethodCall method)
{
InvokeRetriable(DEFAULT_THROTTLED_WAIT_TIMEOUT, method);
}
/// <summary>
/// Invokes a method in a retriable fashion.
/// </summary>
/// <param name="throttledWaitTime">The amount of time to wait if the request is throttled.</param>
/// <param name="method">The method to invoke.</param>
public static void InvokeRetriable(int throttledWaitTime, RetriableMethodCall method)
{
bool retryRequest = false;
do
{
retryRequest = false;
try
{
// Perform some action
method.Invoke();
}
catch (MarketplaceWebServiceOrdersException ordersErr)
{
// If the request is throttled, wait and try again.
if (ordersErr.ErrorCode == "RequestThrottled")
{
Console.WriteLine("Request is throttled; waiting...");
retryRequest = true;
System.Threading.Thread.Sleep(throttledWaitTime);
}
else
{
// On any other error, re-throw the exception to be handled by the caller
throw;
}
}
} while (retryRequest);
}
}
}
I think this may have to do with the way in which you imported (I assume) your web service objects. If it's a WCF service, make sure your "Model" is referenced along the different projects, and when adding the service reference, make sure to check the option "Reuse existing Types".
You have classes MarketplaceWebServiceOrders both in current FetchNewOrdersJob and in MarketplaceWebServiceOrders namespaces (also last namespace have same name as your class). Looks like your OrderFetcher class is declared in MarketplaceWebServiceOrders namespace. And it expects as argument MarketplaceWebServiceOrders which is also declared in that namespace. But you are trying to pass MarketplaceWebServiceOrders class, which is declared in current FetchNewOrdersJob namespace.
Rename your FetchNewOrdersJob.MarketplaceWebServiceOrders class to avoid these conflicts.

Why can I still write to session when SessionStateBehavior.ReadOnly is set on a controller and should I care?

I have a number of controllers with SessionStateBehavior.ReadOnly set on them to enable parallel processing of multiple ajax requests.
But I have noticed that this doesn't stop me from writing to session (unlike SessionStateBehavior.Disabled which throws an exception).
My application uses Microsoft Charting and the chart and image map are generated in response to an ajax request with the chart being stored in session until the image markup is rendered by the browser at which point the image src triggers the browser to request the chart image which is retrieved from session. So there are no issues here with attempting to read from session before its been written.
Everything works fine, I have multiple ajax requests being processed in parallel and my charts are being returned correctly so does it matter that I have declared SessionStateBehavior.ReadOnly when I am writing to session?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using System.Web.UI.DataVisualization.Charting;
using Mdl.Rcm.Web.Areas.Dashboards.Models;
using Mdl.Web.Security;
using Mdl.Persistence.UoW;
using Mdl.Rcm.Business.Dashboards;
using Mdl.Rcm.Business.Dashboards.Models;
using Mdl.Rcm.Web.Areas.Dashboards.Charts;
using Mdl.Rcm.Web.Areas.Dashboards.Charts.OrganisationConnectionsByRole;
using Mdl.Web.Mvc;
namespace Mdl.Rcm.Web.Areas.Dashboards.Controllers
{
/// <summary>
/// Controller for the Organisation Connections By Role chart.
/// </summary>
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
[RedirectingAuthorize(Roles = "Dashboards")]
[Area(IsSiteRoot = false)]
public class ChartOrganisationConnectionsByRoleController : Controller
{
private readonly IRelationshipAndRiskService relationshipAndRiskService;
private readonly IConfigurationService configurationService;
private readonly IOrganisationConnectionsByRoleChartBuilder chartBuilder;
private readonly IListService listService;
private BarConfiguration barConfiguration;
private const string TempDataKey = "OrganisationConnectionsByRoleData";
public ChartOrganisationConnectionsByRoleController(IOrganisationConnectionsByRoleChart organisationConnectionsByRoleChart, IRelationshipAndRiskService relationshipAndRiskService, IConfigurationService configurationService, IOrganisationConnectionsByRoleChartBuilder chartBuilder, IListService listService)
{
this.relationshipAndRiskService = relationshipAndRiskService;
this.configurationService = configurationService;
this.chartBuilder = chartBuilder;
this.listService = listService;
}
/// <summary>
/// Standard Organisation Connections by Role component loader
/// </summary>
/// <param name="listId">The list id.</param>
/// <param name="degree">The active degree.</param>
/// <param name="barIndex">Index of the active bar.</param>
/// <returns></returns>
[HandleAjaxError]
public ActionResult Load(int listId, int degree, int barIndex)
{
using (UnitOfWork.Start("Analysis"))
{
// Get the data
var relationshipPlanningData = GetChartDataForInitialLoadParentComponent(listId);
var connectionsFound = relationshipPlanningData.SeriesModels.Count > 0;
var listName = listService.GetListName(listId).Name;
var information = new InformationModel(degree, true) { ConnectionsFound = connectionsFound, NumberOfOrganisationsInList = relationshipPlanningData.TotalNumberOfOrganisations, ListName = listName };
return PartialView("OrganisationConnectionsByRoleComponent", new OrganisationConnectionsByRoleComponentModel { ListId = listId, ActiveDegree = degree, ActiveBarIndex = barIndex, BarConfiguration = configurationService.GetBarConfiguration(), ConnectionsFound = connectionsFound, Information = information});
}
}
/// <summary>
/// Creates the Connections by Role chart and stores it in Session then returns the chart map.
/// The chart image map is always created first, the chart is created as part of the process of creating the map.
/// </summary>
/// <returns></returns>
public ActionResult Chart(Guid chartId, int listId)
{
using (UnitOfWork.Start("Analysis"))
{
barConfiguration = configurationService.GetBarConfiguration();
var relationshipPlanningData = GetChartDataForInitialLoadChart();
if (relationshipPlanningData.SeriesModels.Count == 0)
{
return PartialView("InfoMessage", "No connections found");
}
// Set up the chart
return GenerateChart(relationshipPlanningData, listId, chartId);
}
}
private ActionResult GenerateChart(OrganisationConnectionsByRoleData relationshipPlanningData, int listId, Guid chartId)
{
var chartAndXPoints = chartBuilder.BuildChart(2, relationshipPlanningData, barConfiguration, SetActiveBarIndex(-1), listId);
// Store the chart in session for retrieval by the browser as the src on an image tag.
ChartSession.GenerateChartToSession(chartId, chartAndXPoints.Chart, Session);
// Get y data for use client side
var yPointsDataView = GetYPointsDataView(relationshipPlanningData);
// Get x co-ordinates for use client side. Must be done after the chart has been generated to session.
var xPointsView = GetXPointsView(chartAndXPoints.XPoints);
// Render the image tag and image map
return this.Chart(chartAndXPoints.Chart, xPointsView + yPointsDataView, chartId, "ConnectionsByRoleImage");
}
/// <summary>
/// Gets the chart data for use by the main component.
/// </summary>
/// <param name="listId">The list id.</param>
/// <returns></returns>
private OrganisationConnectionsByRoleData GetChartDataForInitialLoadParentComponent(int listId)
{
// This is the call from the load action so get the data and store it for use by the chart action to save the performance hit
var data = relationshipAndRiskService.GetOrganisationConnectionsByRoleChartData(listId);
TempData[TempDataKey] = data;
return data;
}
/// <summary>
/// Gets the chart data for use by the main component chart.
/// </summary>
/// <returns></returns>
private OrganisationConnectionsByRoleData GetChartDataForInitialLoadChart()
{
// This call is from the chart action so use the data that was retreived on the load action
return (OrganisationConnectionsByRoleData)TempData[TempDataKey];
}
/// <summary>
/// Return the Connections By Role chart from session.
/// </summary>
/// <returns></returns>
public ActionResult ConnectionsByRoleImage(Guid chartId)
{
var chart = ChartSession.GetChartFromSession(chartId, Session);
return File(chart, "image");
}
/// <summary>
/// Return the Connections By Role chart from session.
/// </summary>
/// <param name="listId">The list id.</param>
/// <param name="degree">The active degree.</param>
/// <param name="barIndex">Index of the active bar.</param>
/// <returns></returns>
public ActionResult ConnectionsByRoleActive(int listId, int degree, int barIndex)
{
using (UnitOfWork.Start("Analysis"))
{
barConfiguration = configurationService.GetBarConfiguration();
// Get the data
var relationshipPlanningData = relationshipAndRiskService.GetOrganisationConnectionsByRoleChartData(listId);
if (relationshipPlanningData.SeriesModels.Count == 0)
{
return PartialView("InfoMessage", "No connections found");
}
// Set up the chart
var chartAndXPoints = chartBuilder.BuildChart(SetActiveDegree(degree), relationshipPlanningData, barConfiguration, SetActiveBarIndex(-1), listId);
var ms = new MemoryStream();
chartAndXPoints.Chart.SaveImage(ms, ChartImageFormat.Png);
return File(ms.ToArray(), "image");
}
}
/// <summary>
/// Gets the graph X points and render them as a javascript object for use by the view.
/// </summary>
/// <param name="xPoints">The x points.</param>
/// <returns></returns>
private string GetXPointsView(List<float> xPoints)
{
var model = new XPointsModel
{
ChartName = "Connections By Role",
Points = xPoints
};
return this.ViewAsString("XPoints", model);
}
/// <summary>
/// Gets the Y points data and render them as a javascript object for use by the view.
/// </summary>
/// <param name="relationshipPlanningData">The relationship planning data.</param>
/// <returns></returns>
private string GetYPointsDataView(OrganisationConnectionsByRoleData relationshipPlanningData)
{
var degree1DataPoints = relationshipPlanningData.SeriesModels[0].DataPoints.Select(p => p.Y).ToList();
var degree2DataPoints = relationshipPlanningData.SeriesModels[1].DataPoints.Select(p => p.Y).ToList();
var model = new YPointsDegree1And2Model
{
ChartName = "Connections By Role",
Degree1Data = degree1DataPoints,
Degree2Data = degree2DataPoints
};
return this.ViewAsString("YPointsDegree1And2", model);
}
private int SetActiveBarIndex(int barIndex)
{
if (barIndex == -1)
{
barIndex = barConfiguration.Bars.First(b => b.IsDefaultActive).Index;
}
return barIndex;
}
private static int SetActiveDegree(int degree)
{
if (degree == -1)
{
degree = 2;
}
return degree;
}
}
}
public class ChartSession
{
public static byte[] GetChartFromSession(Guid chartId, HttpSessionStateBase session)
{
// Get the chart from session
var data = session[chartId.ToString()] as byte[];
// Clear the session
session[chartId.ToString()] = null;
return data;
}
public static void GenerateChartToSession(Guid chartId, Chart chart, HttpSessionStateBase session)
{
var ms = new MemoryStream();
chart.SaveImage(ms, ChartImageFormat.Png);
session[chartId.ToString()] = ms.ToArray();
}
SessionStateBehavior only tells ASP.NET what locks to put on the Session
See this question: Controller SessionStateBehavior is ReadOnly and I can update Session Variable

Regular expression to separate arguments in functions

I can't deal with a regular expression to separate the argument from function.
The function takes arguments in following way:
FunctionName(arg1;arg2;...;argn)
Now to make the rest of my code work I need to do the following-put every argument in ():
FunctionName((arg1);(arg2);(arg3))
The problem is that the arg can be anything- a number, an operator, other function
The test code for the solution is:
The function before regexp:
Function1((a1^5-4)/2;1/sin(a2);a3;a4)+Function2(a1;a2;1/a3)
After i needd to get sth like this:
Function1(((a1^5-4)/2);(1/sin(a2));(a3);(a4))+Function2((a1);(a2);(1/a3))
Unless I'm missing something, isn't it as simple as replacing ; with );( and surrounding the whole thing in ( ) ?
Using Regex:
(?:([^;()]+);?)+
and LINQ:
string result = "FunctionName(" +
String.Join(";",
from Capture capture in
Regex.Matches(inputString, #"FunctionName\((?:([^;()]+);?)+\)")[0].Groups[1].
Captures
select "(" + capture.Value + ")") + ")";
This is a far cry from a Regex but the potential for nested functions combined with the fact that this is a structured language being modified that a lexer/parser scheme is more appropriate.
Here is an example of a system that processes things of this nature
First, we define something that can be located in the input (the expression to modify)
public interface ISourcePart
{
/// <summary>
/// Gets the string representation of the kind of thing we're working with
/// </summary>
string Kind { get; }
/// <summary>
/// Gets the position this information is found at in the original source
/// </summary>
int Position { get; }
/// <summary>
/// Gets a representation of this data as Token objects
/// </summary>
/// <returns>An array of Token objects representing the data</returns>
Token[] AsTokens();
}
Next, we'll define a construct for housing tokens (identifiable portions of the source text)
public class Token : ISourcePart
{
public int Position { get; set; }
public Token[] AsTokens()
{
return new[] {this};
}
public string Kind { get; set; }
/// <summary>
/// Gets or sets the value of the token
/// </summary>
public string Value { get; set; }
/// <summary>
/// Creates a new Token
/// </summary>
/// <param name="kind">The kind (name) of the token</param>
/// <param name="match">The Match the token is to be generated from</param>
/// <param name="index">The offset from the beginning of the file the index of the match is relative to</param>
/// <returns>The newly created token</returns>
public static Token Create(string kind, Match match, int index)
{
return new Token
{
Position = match.Index + index,
Kind = kind,
Value = match.Value
};
}
/// <summary>
/// Creates a new Token
/// </summary>
/// <param name="kind">The kind (name) of the token</param>
/// <param name="value">The value to assign to the token</param>
/// <param name="position">The absolute position in the source file the value is located at</param>
/// <returns>The newly created token</returns>
public static Token Create(string kind, string value, int position)
{
return new Token
{
Kind = kind,
Value = value,
Position = position
};
}
}
We'll use Regexes to find our tokens in this example (below - Excerpt from Program.cs in my demo project).
/// <summary>
/// Breaks an input string into recognizable tokens
/// </summary>
/// <param name="source">The input string to break up</param>
/// <returns>The set of tokens located within the string</returns>
static IEnumerable<Token> Tokenize(string source)
{
var tokens = new List<Token>();
var sourceParts = new[] { new KeyValuePair<string, int>(source, 0) };
tokens.AddRange(Tokenize(OpenParen, "\\(", ref sourceParts));
tokens.AddRange(Tokenize(CloseParen, "\\)", ref sourceParts));
tokens.AddRange(Tokenize(Semi, ";", ref sourceParts));
tokens.AddRange(Tokenize(Operator, "[\\^\\\\*\\+\\-/]", ref sourceParts));
tokens.AddRange(Tokenize(Literal, "\\w+", ref sourceParts));
return tokens.OrderBy(x => x.Position);
}
As you can see, I've defined patterns for open and close parenthesis, semicolons, basic math operators and letters and numbers.
The Tokenize method is defined as follows (again from Program.cs in my demo project)
/// <summary>
/// Performs tokenization of a collection of non-tokenized data parts with a specific pattern
/// </summary>
/// <param name="tokenKind">The name to give the located tokens</param>
/// <param name="pattern">The pattern to use to match the tokens</param>
/// <param name="untokenizedParts">The portions of the input that have yet to be tokenized (organized as text vs. position in source)</param>
/// <returns>The set of tokens matching the given pattern located in the untokenized portions of the input, <paramref name="untokenizedParts"/> is updated as a result of this call</returns>
static IEnumerable<Token> Tokenize(string tokenKind, string pattern, ref KeyValuePair<string, int>[] untokenizedParts)
{
//Do a bit of setup
var resultParts = new List<KeyValuePair<string, int>>();
var resultTokens = new List<Token>();
var regex = new Regex(pattern);
//Look through all of our currently untokenized data
foreach (var part in untokenizedParts)
{
//Find all of our available matches
var matches = regex.Matches(part.Key).OfType<Match>().ToList();
//If we don't have any, keep the data as untokenized and move to the next chunk
if (matches.Count == 0)
{
resultParts.Add(part);
continue;
}
//Store the untokenized data in a working copy and save the absolute index it reported itself at in the source file
var workingPart = part.Key;
var index = part.Value;
//Look through each of the matches that were found within this untokenized segment
foreach (var match in matches)
{
//Calculate the effective start of the match within the working copy of the data
var effectiveStart = match.Index - (part.Key.Length - workingPart.Length);
resultTokens.Add(Token.Create(tokenKind, match, part.Value));
//If we didn't match at the beginning, save off the first portion to the set of untokenized data we'll give back
if (effectiveStart > 0)
{
var value = workingPart.Substring(0, effectiveStart);
resultParts.Add(new KeyValuePair<string, int>(value, index));
}
//Get rid of the portion of the working copy we've already used
if (match.Index + match.Length < part.Key.Length)
{
workingPart = workingPart.Substring(effectiveStart + match.Length);
}
else
{
workingPart = string.Empty;
}
//Update the current absolute index in the source file we're reporting to be at
index += effectiveStart + match.Length;
}
//If we've got remaining data in the working copy, add it back to the untokenized data
if (!string.IsNullOrEmpty(workingPart))
{
resultParts.Add(new KeyValuePair<string, int>(workingPart, index));
}
}
//Update the untokenized data to contain what we couldn't process with this pattern
untokenizedParts = resultParts.ToArray();
//Return the tokens we were able to extract
return resultTokens;
}
Now that we've got the methods and types in place to handle our tokenized data, we need to be able to recognize pieces of larger meaning, like calls to simple functions (like sin(x)), complex functions (like Function1(a1;a2;a3)), basic mathematical operations (like +, -, *, etc.), and so on. We'll make a simple parser for dealing with that; firstly we'll define a match condition for a parse node.
public class ParseNodeDefinition
{
/// <summary>
/// The set of parse node definitions that could be transitioned to from this one
/// </summary>
private readonly IList<ParseNodeDefinition> _nextNodeOptions;
/// <summary>
/// Creates a new ParseNodeDefinition
/// </summary>
private ParseNodeDefinition()
{
_nextNodeOptions = new List<ParseNodeDefinition>();
}
/// <summary>
/// Gets whether or not this definition is an acceptable ending point for the parse tree
/// </summary>
public bool IsValidEnd { get; private set; }
/// <summary>
/// Gets the name an item must have for it to be matched by this definition
/// </summary>
public string MatchItemsNamed { get; private set; }
/// <summary>
/// Gets the set of parse node definitions that could be transitioned to from this one
/// </summary>
public IEnumerable<ParseNodeDefinition> NextNodeOptions
{
get { return _nextNodeOptions; }
}
/// <summary>
/// Gets or sets the tag that will be associated with the data if matched
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Creates a new ParseNodeDefinition matching items with the specified name/kind.
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>A ParseNodeDefinition capable of matching items of the given name</returns>
public static ParseNodeDefinition Create(string matchItemsNamed, string tag, bool isValidEnd)
{
return new ParseNodeDefinition { MatchItemsNamed = matchItemsNamed, Tag = tag, IsValidEnd = isValidEnd };
}
public ParseNodeDefinition AddOption(string matchItemsNamed)
{
return AddOption(matchItemsNamed, string.Empty, false);
}
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag)
{
return AddOption(matchItemsNamed, tag, false);
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag, bool isValidEnd)
{
var node = Create(matchItemsNamed, tag, isValidEnd);
_nextNodeOptions.Add(node);
return node;
}
public ParseNodeDefinition AddOption(string matchItemsNamed, bool isValidEnd)
{
return AddOption(matchItemsNamed, string.Empty, isValidEnd);
}
/// <summary>
/// Links the given node as an option for a state to follow this one in the parse tree this node is a part of
/// </summary>
/// <param name="next">The node to add as an option</param>
public void LinkTo(ParseNodeDefinition next)
{
_nextNodeOptions.Add(next);
}
}
This will let us match a single element by name (whether it's a ParseTree defined later) or a Token as they both implement the ISourcePart interface. Next we'll make a ParseTreeDefinition that allows us to specify sequences of ParseNodeDefinitions for matching.
public class ParseTreeDefinition
{
/// <summary>
/// The set of parse node definitions that constitute an initial match to the parse tree
/// </summary>
private readonly IList<ParseNodeDefinition> _initialNodeOptions;
/// <summary>
/// Creates a new ParseTreeDefinition
/// </summary>
/// <param name="name">The name to give to parse trees generated from full matches</param>
public ParseTreeDefinition(string name)
{
_initialNodeOptions = new List<ParseNodeDefinition>();
Name = name;
}
/// <summary>
/// Gets the set of parse node definitions that constitute an initial match to the parse tree
/// </summary>
public IEnumerable<ParseNodeDefinition> InitialNodeOptions { get { return _initialNodeOptions; } }
/// <summary>
/// Gets the name of the ParseTreeDefinition
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed)
{
return AddOption(matchItemsNamed, string.Empty, false);
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag)
{
return AddOption(matchItemsNamed, tag, false);
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag, bool isValidEnd)
{
var node = ParseNodeDefinition.Create(matchItemsNamed, tag, isValidEnd);
_initialNodeOptions.Add(node);
return node;
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, bool isValidEnd)
{
return AddOption(matchItemsNamed, string.Empty, isValidEnd);
}
/// <summary>
/// Attempts to follow a particular branch in the parse tree from a given starting point in a set of source parts
/// </summary>
/// <param name="parts">The set of source parts to attempt to match in</param>
/// <param name="startIndex">The position to start the matching attempt at</param>
/// <param name="required">The definition that must be matched for the branch to be followed</param>
/// <param name="nodes">The set of nodes that have been matched so far</param>
/// <returns>true if the branch was followed to completion, false otherwise</returns>
private static bool FollowBranch(IList<ISourcePart> parts, int startIndex, ParseNodeDefinition required, ICollection<ParseNode> nodes)
{
if (parts[startIndex].Kind != required.MatchItemsNamed)
{
return false;
}
nodes.Add(new ParseNode(parts[startIndex], required.Tag));
return parts.Count > (startIndex + 1) && required.NextNodeOptions.Any(x => FollowBranch(parts, startIndex + 1, x, nodes)) || required.IsValidEnd;
}
/// <summary>
/// Attempt to match the parse tree definition against a set of source parts
/// </summary>
/// <param name="parts">The source parts to match against</param>
/// <returns>true if the parse tree was matched, false otherwise. parts is updated by this method to consolidate matched nodes into a ParseTree</returns>
public bool Parse(ref IList<ISourcePart> parts)
{
var partsCopy = parts.ToList();
for (var i = 0; i < parts.Count; ++i)
{
var tree = new List<ParseNode>();
if (InitialNodeOptions.Any(x => FollowBranch(partsCopy, i, x, tree)))
{
partsCopy.RemoveRange(i, tree.Count);
partsCopy.Insert(i, new ParseTree(Name, tree.ToArray(), tree[0].Position));
parts = partsCopy;
return true;
}
}
return false;
}
}
Of course these don't do us much good without having some place to store the results of the matchers we've defined so far, so let's define ParseTree and ParseNode where a ParseTree is simply a collection of ParseNode objects where ParseNode is a wrapper around a ParseTree or Token (or more generically any ISourcePart).
public class ParseTree : ISourcePart
{
/// <summary>
/// Creates a new ParseTree
/// </summary>
/// <param name="kind">The kind (name) of tree this is</param>
/// <param name="nodes">The nodes the tree matches</param>
/// <param name="position">The position in the source file this tree is located at</param>
public ParseTree(string kind, IEnumerable<ISourcePart> nodes, int position)
{
Kind = kind;
ParseNodes = nodes.ToList();
Position = position;
}
public string Kind { get; private set; }
public int Position { get; private set; }
/// <summary>
/// Gets the nodes that make up this parse tree
/// </summary>
public IList<ISourcePart> ParseNodes { get; internal set; }
public Token[] AsTokens()
{
return ParseNodes.SelectMany(x => x.AsTokens()).ToArray();
}
}
public class ParseNode : ISourcePart
{
/// <summary>
/// Creates a new ParseNode
/// </summary>
/// <param name="sourcePart">The data that was matched to create this node</param>
/// <param name="tag">The tag data (if any) associated with the node</param>
public ParseNode(ISourcePart sourcePart, string tag)
{
SourcePart = sourcePart;
Tag = tag;
}
public string Kind { get { return SourcePart.Kind; } }
/// <summary>
/// Gets the tag associated with the matched data
/// </summary>
public string Tag { get; private set; }
/// <summary>
/// Gets the data that was matched to create this node
/// </summary>
public ISourcePart SourcePart { get; private set; }
public int Position { get { return SourcePart.Position; } }
public Token[] AsTokens()
{
return SourcePart.AsTokens();
}
}
That's it for the constructs we need, so we'll move into configuring our parse tree definitions. The code from here on is from Program.cs in my demo.
As you might have noticed in the block above about declaring the patterns for each token, there were some values referenced but not defined, here they are.
private const string CloseParen = "CloseParen";
private const string ComplexFunctionCall = "ComplexFunctionCall";
private const string FunctionCallStart = "FunctionCallStart";
private const string Literal = "Literal";
private const string OpenParen = "OpenParen";
private const string Operator = "Operator";
private const string ParenthesisRequiredElement = "ParenthesisRequiredElement";
private const string ParenthesizedItem = "ParenthesizedItem";
private const string Semi = "Semi";
private const string SimpleFunctionCall = "SimpleFunctionCall";
Let's begin by defining a pattern that matches literals (\w+ pattern) that are followed by open parenthesis; we'll use this to match things like sin( or Function1(.
static ParseTreeDefinition CreateFunctionCallStartTree()
{
var tree = new ParseTreeDefinition(FunctionCallStart);
var name = tree.AddOption(Literal);
name.AddOption(OpenParen, true);
return tree;
}
Really not a whole lot to it, setup a tree, add an option for the first thing to match as a Literal, add an option of the next thing to match as an open parenthesis and say that it can end the parse tree.
Now for one that's a little more complex, binary mathematical operations (couldn't think of any unary operations that would need to be included)
static ParseTreeDefinition CreateBinaryOperationResultTree()
{
var tree = new ParseTreeDefinition(Literal);
var parenthesizedItem = tree.AddOption(ParenthesizedItem);
var literal = tree.AddOption(Literal);
var simpleCall = tree.AddOption(SimpleFunctionCall);
var complexCall = tree.AddOption(ComplexFunctionCall);
var #operator = parenthesizedItem.AddOption(Operator);
literal.LinkTo(#operator);
simpleCall.LinkTo(#operator);
complexCall.LinkTo(#operator);
#operator.AddOption(ParenthesizedItem, true);
#operator.AddOption(Literal, true);
#operator.AddOption(SimpleFunctionCall, true);
#operator.AddOption(ComplexFunctionCall, true);
return tree;
}
Here we say that the parse tree can start with a parenthesized item (like (1/2)), a literal (like a5 or 3), a simple call (like sin(4)) or a complex one (like Function1(a1;a2;a3)). In essence we've just defined the options for the left hand operand. Next, we say that the parenthesized item must be followed by an Operator (one of the mathematical operators from the pattern declared way up at the beginning) and, for convenience, we'll say that all of the other options for the left hand operand can progress to that same state (having the operator). Next, the operator must have a right hand side as well, so we give it a duplicate set of options to progress to. Note that they are not the same definitions as the left hand operands, these have the flag set to be able to terminate the parse tree. Notice that the parse tree is named Literal to avoid having to specify yet another kind of element to match all over the place.
Next up, parenthesized items:
static ParseTreeDefinition CreateParenthesizedItemTree()
{
var tree = new ParseTreeDefinition(ParenthesizedItem);
var openParen = tree.AddOption(OpenParen);
var nestedSimpleCall = openParen.AddOption(SimpleFunctionCall);
var nestedComplexCall = openParen.AddOption(ComplexFunctionCall);
var arg = openParen.AddOption(Literal);
var parenthesizedItem = openParen.AddOption(ParenthesizedItem);
var closeParen = nestedSimpleCall.AddOption(CloseParen, true);
arg.LinkTo(closeParen);
parenthesizedItem.LinkTo(closeParen);
nestedComplexCall.LinkTo(closeParen);
return tree;
}
Nice and easy with this one, start with a parenthesis, follow it up with pretty much anything, follow that with another parenthesis to close it.
Simple calls (like sin(x))
static ParseTreeDefinition CreateSimpleFunctionCallTree()
{
var tree = new ParseTreeDefinition(SimpleFunctionCall);
var openParen = tree.AddOption(FunctionCallStart);
var nestedItem = openParen.AddOption(ParenthesizedItem);
var nestedSimpleCall = openParen.AddOption(SimpleFunctionCall);
var nestedComplexCall = openParen.AddOption(ComplexFunctionCall);
var arg = openParen.AddOption(Literal);
var parenthesizedItem = openParen.AddOption(ParenthesizedItem);
var closeParen = nestedSimpleCall.AddOption(CloseParen, true);
arg.LinkTo(closeParen);
nestedItem.LinkTo(closeParen);
parenthesizedItem.LinkTo(closeParen);
nestedComplexCall.LinkTo(closeParen);
return tree;
}
Complex calls (like Function1(a1;a2;a3))
static ParseTreeDefinition CreateComplexFunctionCallTree()
{
var tree = new ParseTreeDefinition(ComplexFunctionCall);
var openParen = tree.AddOption(FunctionCallStart);
var arg = openParen.AddOption(Literal, ParenthesisRequiredElement);
var simpleCall = openParen.AddOption(SimpleFunctionCall, ParenthesisRequiredElement);
var complexCall = openParen.AddOption(ComplexFunctionCall, ParenthesisRequiredElement);
var nested = openParen.AddOption(ParenthesizedItem);
var semi = arg.AddOption(Semi);
simpleCall.LinkTo(semi);
complexCall.LinkTo(semi);
nested.LinkTo(semi);
var arg2 = semi.AddOption(Literal, ParenthesisRequiredElement);
var simpleCall2 = semi.AddOption(SimpleFunctionCall, ParenthesisRequiredElement);
var complexCall2 = semi.AddOption(ComplexFunctionCall, ParenthesisRequiredElement);
var nested2 = semi.AddOption(ParenthesizedItem);
arg2.LinkTo(semi);
simpleCall2.LinkTo(semi);
complexCall2.LinkTo(semi);
nested2.LinkTo(semi);
var closeParen = arg2.AddOption(CloseParen, true);
arg2.LinkTo(closeParen);
simpleCall2.LinkTo(closeParen);
complexCall2.LinkTo(closeParen);
return tree;
}
That's all the trees we'll need, so let's take a look at the code that runs this all
static void Main()
{
//The input string
const string input = #"Function1((a1^5-4)/2;1/sin(a2);a3;a4)+Function2(a1;a2;1/a3)";
//Locate the recognizable tokens within the source
IList<ISourcePart> tokens = Tokenize(input).Cast<ISourcePart>().ToList();
//Create the parse trees we'll need to be able to recognize the different parts of the input
var functionCallStartTree = CreateFunctionCallStartTree();
var parenthethesizedItemTree = CreateParenthesizedItemTree();
var simpleFunctionCallTree = CreateSimpleFunctionCallTree();
var complexFunctionCallTree = CreateComplexFunctionCallTree();
var binaryOpTree = CreateBinaryOperationResultTree();
//Parse until we can't parse anymore
while (functionCallStartTree.Parse(ref tokens) || binaryOpTree.Parse(ref tokens) || parenthethesizedItemTree.Parse(ref tokens) || simpleFunctionCallTree.Parse(ref tokens) || complexFunctionCallTree.Parse(ref tokens))
{ }
//Run our post processing to fix the parenthesis in the input
FixParenthesis(ref tokens);
//Collapse our parse tree(s) back to a string
var values = tokens.OrderBy(x => x.Position).SelectMany(x => x.AsTokens()).Select(x => x.Value);
//Print out our results and wait
Console.WriteLine(string.Join(string.Empty, values));
Console.ReadLine();
}
The only thing we've got left to define is how to actually do the wrapping of the elements in the argument list of a "complex" call. That's handled by the FixParenthesis method.
private static void FixParenthesis(ref IList<ISourcePart> items)
{
//Iterate through the set we're examining
for (var i = 0; i < items.Count; ++i)
{
var parseNode = items[i] as ParseNode;
//If we've got a parse node...
if (parseNode != null)
{
var nodeTree = parseNode.SourcePart as ParseTree;
//If the parse node represents a parse tree...
if (nodeTree != null)
{
//Fix parenthesis within the tree
var nodes = nodeTree.ParseNodes;
FixParenthesis(ref nodes);
nodeTree.ParseNodes = nodes;
}
//If this parse node required parenthesis, replace the subtree and add them
if (parseNode.Tag == ParenthesisRequiredElement)
{
var nodeContents = parseNode.AsTokens();
var combined = string.Join(string.Empty, nodeContents.OrderBy(x => x.Position).Select(x => x.Value));
items[i] = Token.Create(parseNode.Kind, string.Format("({0})", combined), parseNode.Position);
}
continue;
}
var parseTree = items[i] as ParseTree;
//If we've got a parse tree...
if (parseTree != null)
{
//Fix parenthesis within the tree
var nodes = parseTree.ParseNodes;
FixParenthesis(ref nodes);
parseTree.ParseNodes = nodes;
}
}
}
At any rate, I hope this has helped or at least provided a fun diversion.
I probably managed to deal with it(now testing). It turned out to be 5-stage operation. Assuming that '{' and ';' cannot occur in function I've done sth like this:
sBuffer = Regex.Replace(sBuffer, #"(?<sep>[;])", "};{");
sBuffer = Regex.Replace(sBuffer, #"([(])(?<arg>.+?)[}]", "({${arg}}");
sBuffer = Regex.Replace(sBuffer, #"([;])(?<arg>.+?)([)]){1}", ";${arg}})");
sBuffer = Regex.Replace(sBuffer, #"{", "(");
sBuffer = Regex.Replace(sBuffer, #"}", ")");
0.
function1((a1^5-4)/2;1/sin(a2);a3;a4)+function2(a1;a2;1/a3)'
1.First line replaces ; with };{
function1((a1^5-4)/2};{1/sin(a2)};{a3};{a4)+function2(a1};{a2};{1/a3)
2.For first argument - after ( or (not intended) arguments which contain ')' replace (arg};with ({arg}:
function1({(a1^5-4)/2};{1/sin({a2)};{a3};{a4)+function2({a1};{a2};{1/a3)
3. The same at the and of function: {arg) with {arg}:
function1({(a1^5-4)/2};{1/sin({a2})};{a3};{a4})+function2({a1};{a2};{1/a3})
4.5. Replace '{' and '}' with '(' ')':
function1(((a1^5-4)/2);(1/sin((a2)));(a3);(a4))+function2((a1);(a2);(1/a3))
We have some extra () specially when argument itself is surrounded by '(' ')' (nested function) but it doesn't metter as the code is then proceed by Reversed Polish Notation
This is my first code for regexp(I found out about rgexp just few days ago- I'm a beginer) . I hope it's satisfies all the cases (at least those that can occur in excel formulas)

Categories