XML Outputter adds extra non ascii character - c#

I am using following XML outputter for writing xml files on basis of CSV data.
public override void Output(IRow input, IUnstructuredWriter output)
{
IColumn badColumn = input.Schema.FirstOrDefault(col => col.Type != typeof(string));
if (badColumn != null)
{
throw new ArgumentException(string.Format("Column '{0}' must be of type 'string', not '{1}'", badColumn.Name, badColumn.Type.Name));
}
using (var writer = XmlWriter.Create(output.BaseStream, this.fragmentSettings))
{
writer.WriteStartElement(this.rowPath);
foreach (IColumn col in input.Schema)
{
var value = input.Get<string>(col.Name);
if (value != null)
{
// Skip null values in order to distinguish them from empty strings
writer.WriteElementString(this.columnPaths[col.Name] ?? col.Name, value);
}
}
}
}
It works really fine and jobs finishes completely without any errors however, on preview and downloading the file there is another extra character which causes in failure of that xml file being read. I have tried with fragment level and Auto as conformance levels.
My sample output obtained is
and the extra character between the 2 tags is causing problem while reading the file.

I have solved the problem by explicitly providing the encoding settings as well as closing tags with the code below
private XmlWriterSettings fragmentSettings = new XmlWriterSettings
{
ConformanceLevel = ConformanceLevel.Auto,
Encoding = Encoding.UTF8
};
public override void Output(IRow input, IUnstructuredWriter output)
{
IColumn badColumn = input.Schema.FirstOrDefault(col => col.Type != typeof(string));
if (badColumn != null)
{
throw new ArgumentException(string.Format("Column '{0}' must be of type 'string', not '{1}'", badColumn.Name, badColumn.Type.Name));
}
using (var writer = XmlWriter.Create(output.BaseStream, this.fragmentSettings))
{
writer.WriteStartElement(this.rowPath);
foreach (IColumn col in input.Schema)
{
var value = input.Get<string>(col.Name);
if (value != null)
{
// Skip null values in order to distinguish them from empty strings
writer.WriteElementString(this.columnPaths[col.Name] ?? col.Name, value);
}
}
writer.WriteEndElement(); //explicit closing tag for stream
}
}
This outputs a well formed XML which can be easily read with any xml reader.

Related

How to create a CSV file from a XML file

I am very new at C#. In my project I need to create a csv file which will get data from a xml data. Now, I can get data from XML, and print in looger for some particulaer attributes from xml. But I am not sure how can I store my Data into CSV file for that particular attribues.
Here is my XML file that I need to create a CSV file.
<?xml version="1.0" encoding="utf-8"?>
<tlp:WorkUnits xmlns:tlp="http://www.timelog.com/XML/Schema/tlp/v4_4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.timelog.com/XML/Schema/tlp/v4_4 http://www.timelog.com/api/xsd/WorkUnitsRaw.xsd">
<tlp:WorkUnit ID="130">
<tlp:EmployeeID>3</tlp:EmployeeID>
<tlp:AllocationID>114</tlp:AllocationID>
<tlp:TaskID>239</tlp:TaskID>
<tlp:ProjectID>26</tlp:ProjectID>
<tlp:ProjectName>LIK Template</tlp:ProjectName>
<tlp:CustomerId>343</tlp:CustomerId>
<tlp:CustomerName>Lekt Corp Inc.</tlp:CustomerName>
<tlp:IsBillable>1</tlp:IsBillable>
<tlp:ApprovedStatus>0</tlp:ApprovedStatus>
<tlp:LastModifiedBy>AL</tlp:LastModifiedBy>
</tlp:WorkUnit>
And my Code where I am getting this value in logger.But I am not sure how can I create a csv file that stores that value in order.
Edited
namespace TimeLog.ApiConsoleApp
{
/// <summary>
/// Template class for consuming the reporting API
/// </summary>
public class ConsumeReportingApi
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(ConsumeReportingApi));
public static void Consume()
{
if (ServiceHandler.Instance.TryAuthenticate())
{
if (Logger.IsInfoEnabled)
{
Logger.Info("Successfully authenticated on reporting API");
}
var customersRaw = ServiceHandler.Instance.Client.GetWorkUnitsRaw(ServiceHandler.Instance.SiteCode,
ServiceHandler.Instance.ApiId,
ServiceHandler.Instance.ApiPassword,
WorkUnit.All,
Employee.All,
Allocation.All,
Task.All,
Project.All,
Department.All,
DateTime.Now.AddDays(-5).ToString(),
DateTime.Now.ToString()
);
if (customersRaw.OwnerDocument != null)
{
var namespaceManager = new XmlNamespaceManager(customersRaw.OwnerDocument.NameTable);
namespaceManager.AddNamespace("tlp", "http://www.timelog.com/XML/Schema/tlp/v4_4");
var workUnit = customersRaw.SelectNodes("tlp:WorkUnit", namespaceManager);
var output = new StringBuilder();
output.AppendLine("AllocationID,ApprovedStatus,CustomerId,CustomerName,EmployeeID");
if (workUnit != null)
{
foreach (XmlNode customer in workUnit)
{
var unit = new WorkUnit();
var childNodes = customer.SelectNodes("./*");
if (childNodes != null)
{
foreach (XmlNode childNode in childNodes)
{
if (childNode.Name == "tlp:EmployeeID")
{
unit.EmployeeID = Int32.Parse(childNode.InnerText);
}
if (childNode.Name == "tlp:EmployeeFirstName")
{
unit.EmployeeFirstName = childNode.InnerText;
}
if (childNode.Name == "tlp:EmployeeLastName")
{
unit.EmployeeLastName = childNode.InnerText;
}
if (childNode.Name == "tlp:AllocationID")
{
unit.AllocationID = Int32.Parse(childNode.InnerText);
}
if (childNode.Name == "tlp:TaskName")
{
unit.TaskName = childNode.InnerText;
}
}
}
output.AppendLine($"{unit.EmployeeID},{unit.EmployeeFirstName},{unit.EmployeeLastName},{unit.AllocationID},{unit.TaskName}");
//Console.WriteLine("---");
}
Console.WriteLine(output.ToString());
File.WriteAllText("c:\\...\\WorkUnits.csv", output.ToString());
}
}
else
{
if (Logger.IsWarnEnabled)
{
Logger.Warn("Failed to authenticate to reporting API");
}
}
}
}
}
}
You want to write the columns in the correct order to the CSV (of course), so you need to process them in the correct order. Two options:
intermediate class
Create a new class (let's call it WorkUnit) with properties for each of the columns that you want to write to the CSV. Create a new instance for every <tlp:WorkUnit> node in your XML and fill the properties when you encounter the correct subnodes. When you have processed the entire WorkUnit node, write out the properties in the correct order.
var output = new StringBuilder();
foreach (XmlNode customer in workUnit)
{
// fresh instance of the class that holds all columns (so all properties are cleared)
var unit = new WorkUnit();
var childNodes = customer.SelectNodes("./*");
if (childNodes != null)
{
foreach (XmlNode childNode in childNodes)
{
if(childNode.Name== "tlp:EmployeeID")
{
// employeeID node found, now write to the corresponding property:
unit.EmployeeId = childNode.InnerText;
}
// etc for the other XML nodes you are interested in
}
// all nodes have been processed for this one WorkUnit node
// so write a line to the CSV
output.AppendLine($"{unit.EmployeeId},{unit.AllocationId}, etc");
}
read in correct order
Instead of using foreach to loop through all subnodes in whatever order they appear, search for specific subnodes in the order you want. Then you can write out the CSV in the same order. Note that even when you don't find some subnode, you still need to write out the separator.
var output = new StringBuilder();
foreach (XmlNode customer in workUnit)
{
// search for value for first column (EmployeeID)
var node = workUnit.SelectSingleNode("tlp:EmployeeID");
if (node != null)
{
output.Append(node.InnerText).Append(',');
}
else
{
output.Append(','); // no content, but we still need a separator
}
// etc for the other columns
And of course watch out for string values that contain the separator.
Assuming that you put your XML data into List
StringBuilder str = new StringBuilder();
foreach (var fin list.ToList())
{
str.Append(fin.listfield.ToString() + ",");
}
to create a new line:
str.Replace(",", Environment.NewLine, str.Length - 1, 1);
to save:
string filename=(DirectoryPat/filename.csv");
File.WriteAllText(Filename, str.ToString());
Try this:
var output = new StringBuilder();
output.AppendLine("AllocationID,ApprovedStatus,CustomerId,CustomerName,EmployeeID");
if (workUnit != null)
{
foreach (XmlNode customer in workUnit)
{
var unit = new WorkUnit();
var childNodes = customer.SelectNodes("./*");
if (childNodes != null)
{
for (int i = 0; i<childNodes.Count; ++i)
{
XmlNode childNode = childNodes[i];
if (childNode.Name == "tlp:EmployeeID")
{
unit.EmployeeID = Int32.Parse(childNode.InnerText);
}
if (childNode.Name == "tlp:EmployeeFirstName")
{
unit.EmployeeFirstName = childNode.InnerText;
}
if (childNode.Name == "tlp:EmployeeLastName")
{
unit.EmployeeLastName = childNode.InnerText;
}
if (childNode.Name == "tlp:AllocationID")
{
unit.AllocationID = Int32.Parse(childNode.InnerText);
}
if (childNode.Name == "tlp:TaskName")
{
unit.TaskName = childNode.InnerText;
}
output.Append(childNode.InnerText);
if (i<childNodes.Count - 1)
output.Append(",");
}
output.Append(Environment.NewLine);
}
}
Console.WriteLine(output.ToString());
File.WriteAllText("c:\\Users\\mnowshin\\projects\\WorkUnits.csv", output.ToString());
}
You can use this sequence:
a. Deserialize (i.e. convert from XML to C# objects) your XML.
b. Write a simple loop to write the data to a file.
The advantages of this sequence:
You can use a list of your data/objects "readable" that you can add any other access code to it.
If you XML schema changed at any time, you can maintain the code very easily.
The solution
a. Desrialize:
Copy you XML file contents. Note You should modify your XML input before coping it.. You should double the WorkUnit node, in order to tell Visual Studio that you would have a list of this node nested inside WorkUnits node.
From Visual Studio Menus select Edit -> Paste Special -> Paste XML as Classes.
Use the deserialize code.
var workUnitsNode = customersRaw.SelectSingleNode("tlp:WorkUnits", namespaceManager);
XmlSerializer ser = new XmlSerializer(typeof(WorkUnits));
WorkUnits workUnits = (WorkUnits)ser.Deserialize(workUnitsNode);
b. Write the csv file
StringBuilder csvContent = new StringBuilder();
// add the header line
csvContent.AppendLine("AllocationID,ApprovedStatus,CustomerId,CustomerName,EmployeeID");
foreach (var unit in workUnits.WorkUnit)
{
csvContent.AppendFormat(
"{0}, {1}, {2}, {3}, {4}",
new object[]
{
unit.AllocationID,
unit.ApprovedStatus,
unit.CustomerId,
unit.CustomerName,
unit.EmployeeID
// you get the idea
});
csvContent.AppendLine();
}
File.WriteAllText(#"G:\Projects\StackOverFlow\WpfApp1\WorkUnits.csv", csvContent.ToString());
You can use Cinchoo ETL - if you have room to use open source library
using (var csvWriter = new ChoCSVWriter("sample1.csv").WithFirstLineHeader())
{
using (var xmlReader = new ChoXmlReader("sample1.xml"))
csvWriter.Write(xmlReader);
}
Output:
ID,tlp_EmployeeID,tlp_AllocationID,tlp_TaskID,tlp_ProjectID,tlp_ProjectName,tlp_CustomerId,tlp_CustomerName,tlp_IsBillable,tlp_ApprovedStatus,tlp_LastModifiedBy
130,3,114,239,26,LIK Template,343,Lekt Corp Inc.,1,0,AL
Disclaimer: I'm the author of this library.

How to compare two FlowDocuments?

I want to compare a FlowDocument to a document of Rich Text Box. Here is the code
if (rtbEditor.Document != (XamlReader.Parse(currentNote.content) as FlowDocument))
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
At the beginning I set rtbEditor's document as
rtbEditor.Document = XamlReader.Parse(currentNote.content) as FlowDocument;
Thus, unless the content of rtbEditor is changed, I thought that the if statement should not execute,but it does. Probably this is not the way to compare FlowDocuments. If this is not the correct way then how can we compare two documents?
If it is necessary, the currentNote.content is a string containing xml content of FlowDocument.
Assuming you have no images in your FlowDocument instances, you can just serialize to XAML and compare the XAML. First, create extension methods to generate the XAML strings:
public static class FrameworkContentElementExtensions
{
public static string ToXaml(this FrameworkContentElement element) // For instance, a FlowDocument
{
if (element == null)
return null;
var sb = new StringBuilder();
using (var xmlWriter = XmlWriter.Create(sb))
{
XamlWriter.Save(element, xmlWriter);
}
return sb.ToString();
}
public static string ToFormattedXamlString(this FrameworkContentElement element)
{
if (element == null)
return null;
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " };
var sb = new StringBuilder();
using (var xmlWriter = XmlWriter.Create(sb, settings))
{
XamlWriter.Save(element, xmlWriter);
}
return sb.ToString();
}
}
Then you can do
if (rtbEditor.Document.ToXaml() != currentNote.content)
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
Note that if the XAML differs only because of cosmetic formatting (XML indentation), since XAML documents are valid XML, you can parse your XAML to an XElement and use XNode.DeepEquals(). You can also serialize a FrameworkContentElement directly to an XElement without the intervening string representation for improved performance:
public static class FrameworkContentElementExtensions
{
public static XElement ToXamlXElement(this FrameworkContentElement element) // For instance, a FlowDocument
{
if (element == null)
return null;
var doc = new XDocument();
using (var xmlWriter = doc.CreateWriter())
{
XamlWriter.Save(element, xmlWriter);
}
var xElement = doc.Root;
if (xElement != null)
xElement.Remove();
return xElement;
}
}
And then
var docXaml = rtbEditor.Document.ToXamlXElement();
var currentNoteXaml = XElement.Parse(currentNote.content);
if (!XNode.DeepEquals(docXaml, currentNoteXaml))
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
If you are concerned there might be embedded messages and want to generate a warning message in this case, see Finding all Images in a FlowDocument.

Iterate through a specific controls property c#

i need to iterate through a specific controls property and save the that control's property name & value in xml file. i wrote few line but getting error.
private void SaveStyle()
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(Application.ExecutablePath+ #"\Products.xml", settings);
PropertyInfo[] properties = metroStyleManager1.GetType().GetProperties();
writer.WriteStartDocument();
foreach (PropertyInfo pi in properties)
{
writer.WriteElementString(pi.Name,pi.GetValue(((object)metroStyleManager1),null));
}
}
this line is giving error writer.WriteElementString(pi.Name,pi.GetValue(((object)metroStyleManager1),null));
next issue which i need to do that i have to read back the controls property data from xml file and set the value as per the controls name. which is not clear to me that how to do it. so please help. thanks
UPDATE
my full code to save controls property & read back too.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml;
using MetroFramework;
namespace CSRAssistant
{
class Utils
{
public static void SaveProperty(System.ComponentModel.Component _Control)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml", settings);
PropertyInfo[] properties = _Control.GetType().GetProperties();
writer.WriteStartElement("metroStyleManager");
foreach (PropertyInfo pi in properties)
{
writer.WriteElementString(pi.Name, Convert.ToString(pi.GetValue(_Control, null)));
}
writer.WriteEndDocument();
writer.Flush();
writer.Close();
}
public static void ReadProperty(System.ComponentModel.Component _Control)
{
string _property = "", _value = "";
if (System.IO.File.Exists(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml"))
{
XmlReader rdr = XmlReader.Create(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml");
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
if (rdr.LocalName.ToUpper() != "METROSTYLEMANAGER")
{
_property = rdr.LocalName;
_value = rdr.ReadInnerXml();
if (_property.ToUpper() == "STYLE")
((MetroFramework.Components.MetroStyleManager)_Control).Style = (MetroColorStyle)Enum.Parse(typeof(MetroColorStyle), _value);
if (_property.ToUpper() == "THEME")
((MetroFramework.Components.MetroStyleManager)_Control).Theme = (MetroThemeStyle)Enum.Parse(typeof(MetroThemeStyle), _value);
//else
// _Control.GetType().GetProperty(_property).SetValue(_Control, _value, null);
}
}
}
rdr.Close();
}
}
}
}
writer.WriteElementString(string localName, string value)
expects two string arguments. But pi.GetValue() returns value of type object. You need to convert second parameter to string:
Convert.ToString(pi.GetValue(metroStyleManager1))
That will check if value of object is not null, and return empty string if value is null. It also will check if object implements IConvertible or IFormattable interfaces and call appropriate ToString() method.
The exact error would surely help, but I guess your problem is the WriteElementString method which takes two string parameters.
PropertyInfo.GetValue on the other hand returns an object. You have to convert that object to a string. A possible way would be to call .ToString() on it if it's not null and use an empty string if it is.
foreach (PropertyInfo pi in properties)
{
object obj = pi.GetValue(metroStyleManager1, null);
writer.WriteElementString(pi.Name, obj != null ? obj.ToString() : String.Empty);
}

How to refresh Formatting on Non-Calculated field and refresh Calculated fields in Fillable PDF form

I have a PDF Template file that I am trying to populate with contents of "MyDocument". All the fields populate fine but the problem is that the "Calculated" fields in my PDF are not refreshed nor is the formatting set on other fields. How do I make the calculated fields refresh and formatting to work using ITextSharp? (I do not care if I get a C# or VB.NET answer)
VB.NET:
Public Shared Sub Serialize(ByVal stmt As MyDocument, ByVal file As FileInfo)
Dim reader As New PdfReader(TemplateFilePath.FullName)
Dim pdfStamper As New PdfStamper(reader, New FileStream(file.FullName, FileMode.Open))
Try
With itsDaDetailFields
.MoveFirst()
While Not .EOF
Dim pdfFieldName As String = NsT(Of String)(!PDFFieldName, Nothing)
If Not String.IsNullOrEmpty(pdfFieldName) Then
Dim value As String = NsT(Of String)(stmt.GetValueFromPDFField(pdfFieldName), Nothing)
If Not String.IsNullOrEmpty(value) Then
pdfStamper.AcroFields.SetField(pdfFieldName, value)
End If
End If
.MoveNext()
End While
End With
Finally
pdfStamper.FormFlattening = False
reader.Close()
pdfStamper.Close()
End Try
End Sub
C#:
public static void Serialize(MyDocument stmt, FileInfo file)
{
PdfReader reader = new PdfReader(TemplateFilePath.FullName);
PdfStamper pdfStamper = new PdfStamper(reader, new FileStream(file.FullName, FileMode.Open));
try {
var _with1 = itsDaDetailFields;
_with1.MoveFirst();
while (!_with1.EOF) {
string pdfFieldName = NsT<string>(_with1["PDFFieldName"], null);
if (!string.IsNullOrEmpty(pdfFieldName)) {
string value = NsT<string>(stmt.GetValueFromPDFField(pdfFieldName), null);
if (!string.IsNullOrEmpty(value)) {
pdfStamper.AcroFields.SetField(pdfFieldName, value);
}
}
_with1.MoveNext();
}
} finally {
pdfStamper.FormFlattening = false;
reader.Close();
pdfStamper.Close();
}
}
So I figured out how to do it in .NET based on the following post using iText (the java version of ITextSharp - the procedure is just slightly different for .net). Feel free to read the following thread for a complete explanation and discussion of the same problem in iText:
http://itext-general.2136553.n4.nabble.com/Setting-acroform-value-via-iText-messes-with-acrofield-formating-td2167101.html
There are 2 ways to do it:
(1) Provide the display value like:
pdfStamper.AcroFields.SetField(pdfFieldName, value, <formatted value>)
as in:
pdfStamper.AcroFields.SetField(pdfFieldName, 1000, "1,000")
This wasn't optimal for me because I couldn't figure out programattically from my PDF file which textboxes were formatting their contents in which format. Some had slightly different formats (some had 2 decimal places, some had 0, some had many) so if you could keep track of how a textbox formats its data or if they all do the same thing then this might work. This also didn't fix the calculated fields problem, just seemed to fix the formatting problem.
(2) Provide javascript to "DIRTY" the value so it gets formatted & calculated:
My code turned into something like the following since I only needed to format numeric values but this can be expanded to handle other types (see discussion below).
Dim reader As New PdfReader(TemplateFilePath.FullName)
Dim pdfStamper As New PdfStamper(reader, New FileStream(file.FullName, FileMode.Open))
With pdfStamper.AcroFields
If IsNumeric(value) Then
Dim js As String = String.Format("var f = this.getField('{0}'); f.value = 1 * f.value;", pdfFieldName)
pdfStamper.JavaScript = js
End If
.SetField(pdfFieldName, value)
End With
reader.Close()
pdfStamper.Close()
So the trick is that you need to use JavaScript to get the value dirty, then Reader will apply the formatting. You can generalize this more and handle more value types based on the complete solution provided below (sorry it is in java but can be adapted to .net):
import java.io.IOException;
import java.util.ArrayList;
import com.lowagie.text.pdf.PRStream;
import com.lowagie.text.pdf.PdfDictionary;
import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfObject;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfString;
import com.lowagie.text.pdf.AcroFields.Item;
public class AcroFieldJSScanner {
protected ArrayList<String> functions = null;
public void getFieldFunctions(Item item) throws IOException{
PdfDictionary dict;
for (int i = 0; i < item.size(); i++) {
dict = item.getMerged(i);
scanPdfDictionary(dict);
// dict = item.getWidget(i);
//
// scanPdfDictionary(dict);
}
}
protected void scanPdfDictionary(PdfDictionary dict) throws IOException{
PdfObject objJS = null;
String func = null;
objJS = dict.get(PdfName.JS);
if (dict.get(PdfName.S) != null && objJS != null && objJS.isString()){
PdfString strJS = (PdfString)objJS;
if (functions == null){
functions = new ArrayList<String>();
}
func = strJS.toString();
functions.add(func);
}else if (dict.get(PdfName.S) != null && objJS != null){
for(Object obj : dict.getKeys()){
PdfName pdfName = (PdfName)obj;
PdfObject pdfObj = dict.get(pdfName);
if (pdfObj.isIndirect()){
PdfObject pdfIndirectObject = PdfReader.getPdfObject(pdfObj);
func = new String(PdfReader.getStreamBytes((PRStream)pdfIndirectObject));
if (functions == null){
functions = new ArrayList<String>();
}
functions.add(func);
}else{
scanPdfObject(pdfObj);
}
}
}else{
for(Object obj : dict.getKeys()){
PdfName pdfName = (PdfName)obj;
PdfObject pdfObj = dict.get(pdfName);
scanPdfObject(pdfObj);
}
}
}
protected void scanPdfObject(PdfObject parentPdfObject) throws IOException{
if (parentPdfObject.isDictionary()){
scanPdfDictionary((PdfDictionary)parentPdfObject);
}else if (parentPdfObject.isIndirect()){
PdfObject pdfObject = PdfReader.getPdfObject(parentPdfObject);
scanPdfObject(pdfObject);
}
}
public ArrayList<String> getFunctions() {
return functions;
}
public String toString(){
StringBuilder sb = null;
if (getFunctions() != null){
sb = new StringBuilder();
for (int i =0; i< getFunctions().size();i++) {
sb.append(getFunctions().get(i)).append("\n");
}
}else{
return "No functions found";
}
return sb.toString();
}
}
And then if you know the javascript scripts that Adobe will call (using the above code) you know what type the data is so you can "DIRTY" the data. Here are some adobe data types and the javascript that is behind those data types:
public String getFieldFormat(Item item){
PdfDictionary aa = (PdfDictionary) item.getMerged(0).get(PdfName.AA);
if (null != aa)
{
PdfDictionary f = (PdfDictionary)PdfReader.getPdfObject(aa.get(PdfName.F));
if (null != f)
{
PdfString js = (PdfString)PdfReader.getPdfObject(f.get(PdfName.JS));
if (null != js)
{
String sScriptName = js.toString();
if (sScriptName.contains("AFNumber_Format"))
System.out.println("Format : Number");
else if (sScriptName.contains("AFDate_Format"))
System.out.println("Format : Date");
else if (sScriptName.contains("AFTime_Format"))
System.out.println("Format : Time");
else if (sScriptName.contains("AFSpecial_Format"))
System.out.println("Format : Special");
else if (sScriptName.contains("AFPercent_Format"))
System.out.println("Format : Percent");
else
System.out.println("Format : Custom");;
System.out.println("JS: ");
System.out.println(js);
}
}
}
}
In my case, I found out that:
Using FormFlattening prevents the javascript from updating the field
Setting the field using SetField does not trigger the formatting.
So I had to change the javascript to actually write the complete value instead of just dirtying it. Like so:
JS &= String.Format("var f = this.getField('{0}'); f.value = '{1}';",
FieldName.Key, NewFieldValue)
I append the javascript code for each field into the string JS, and then I call pdfStamper.Javascript = JS at the end.

Is it possible to have dynamic object properties in .net 4.0

I want to add new properties to an object based on loop iterations, is this possible in .Net? The reason I want to do this is I am looping through rows in an excel spreadsheet and for every successfully read row I want to create a new dynamic object property. So when the loop is complete I can simply pass the object to a method and log all the records.
See below for code so far:
protected void ReadData(string filePath, bool upload)
{
StringBuilder sb = new StringBuilder();
#region upload
if (upload == true) // CSV file upload chosen
{
using (CsvReader csv = new CsvReader(new StreamReader(filePath), true)) // Cache CSV file to memory
{
int fieldCount = csv.FieldCount; // Total number of fields per row
string[] headers = csv.GetFieldHeaders(); // Correct CSV headers stored in array
SortedList<int, string> errorList = new SortedList<int, string>(); // This list will contain error values
ORCData data = new ORCData();
bool errorFlag = false;
int errorCount = 0;
// Check if headers are correct first before reading data
if (headers[0] != "first name" || headers[1] != "last name" || headers[2] != "job title" || headers[3] != "email address" || headers[4] != "telephone number" || headers[5] != "company" || headers[6] != "research manager" || headers[7] != "user card number")
{
sb.Append("Headers are incorrect");
}
else
{
while (csv.ReadNextRecord())
try
{
//Check csv obj data for valid values
for (int i = 0; i < fieldCount; i++)
{
if (i == 0 || i == 1) // FirstName and LastName
{
if (Regex.IsMatch(csv[i].ToString(), "^[a-z]+$", RegexOptions.IgnoreCase) == false) //REGEX letters only min of 5 char max of 20
{
errorList.Add(errorCount, csv[i]);
errorCount += 1;
errorFlag = true;
string text = csv[i].ToString();
}
}
}
if (errorFlag == true)
{
sb.Append("<b>" + "Number of Error: " + errorCount + "</b>");
sb.Append("<ul>");
foreach (KeyValuePair<int, string> key in errorList)
{
sb.Append("<li>" + key.Value + "</li>");
}
}
else // All validation checks equaled to false. Create User
{
string message = ORCLdap.CreateUserAccount(rootLDAPPath, svcUsername, svcPassword, csv[0], csv[1], csv[2], csv[3], csv[4], csv[5], csv[7]);
// TODO: Add to object here
sb.Append(message);
//sb.Append("<b>New user data uploaded successfully</b>");
}
}// end of try
catch (Exception ex)
{
sb.Append(ex.ToString());
}
finally
{
lblMessage.Text = sb.ToString();
sb.Remove(0, sb.Length);
hdnRdoSelection.Value = "1";
}
}
}
}
#endregion
I have never tried to do this before so I am not sure how I would approach it but any help would be great. Thanks.
I want to add new properties to an object based on loop iterations, is this possible in .Net?
Sort of. You probably want to use ExpandoObject, treating it as an IDictionary<string, object> when you're adding the properties.
Having said that, if you're not going to try to use those properties as properties later, do you actually need them to be properties at all? Why not just use a Dictionary<string, object> to start with?
Yes, you could use ExpandoObject for this. Simply assign the properties you want and it will assume those properties.
dynamic settings= new ExpandoObject();

Categories