I have a infopath form that may contain multiple attachments: By using a group of repeating elements, the user can click an “click add item” option he will be able to upload more attachments.
In Sharepoint I am using a workflow to extract the attachments and put them in a separate list. So far I manage only to extract the first one and the workflow finishes successfully.
can I put a loop or something to iterate trough the form?
I attach the code below:
public sealed partial class FileCopyFeature : SharePointSequentialWorkflowActivity
{
public FileCopyFeature()
{
InitializeComponent();
}
public Guid workflowId = default(System.Guid);
public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties workflowProperties = new Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();
private void CopyFile(object sender, EventArgs e)
{
// Retrieve the file associated with the item
// on which the workflow has been instantiated
SPFile file = workflowProperties.Item.File;
if (file == null)
return;
// Get the binary data of the file
byte[] xmlFormData = null;
xmlFormData = file.OpenBinary();
// Load the data into an XPathDocument object
XPathDocument ipForm = null;
if (xmlFormData != null)
{
using (MemoryStream ms = new MemoryStream(xmlFormData))
{
ipForm = new XPathDocument(ms);
ms.Close();
}
}
if (ipForm == null)
return;
// Create an XPathNavigator object to navigate the XML
XPathNavigator ipFormNav = ipForm.CreateNavigator();
ipFormNav.MoveToFollowing(XPathNodeType.Element);
XmlNamespaceManager nsManager =
new XmlNamespaceManager(new NameTable());
foreach (KeyValuePair<string, string> ns
in ipFormNav.GetNamespacesInScope(XmlNamespaceScope.All))
{
if (ns.Key == String.Empty)
{
nsManager.AddNamespace("def", ns.Value);
}
else
{
nsManager.AddNamespace(ns.Key, ns.Value);
}
}
do
{
XPathNavigator nodeNav = ipFormNav.SelectSingleNode("//my:field2", nsManager);
// Retrieve the value of the attachment in the InfoPath form
//XPathNavigator nodeNav = ipFormNav.SelectSingleNode(
//"//my:field2", nsManager);
string ipFieldValue = string.Empty;
if (nodeNav != null)
{
ipFieldValue = nodeNav.Value;
// Decode the InfoPath file attachment
InfoPathAttachmentDecoder dec =
new InfoPathAttachmentDecoder(ipFieldValue);
string fileName = dec.Filename;
byte[] data = dec.DecodedAttachment;
// Add the file to a document library
using (SPWeb web = workflowProperties.Web)
{
SPFolder docLib = web.Folders["Doc"];
docLib.Files.Add(fileName, data);
docLib.Update();
// workflowProperties.Item.CopyTo(data + "/Doc/" + fileName);
}
}
}
while (ipFormNav.MoveToNext());
}
}
/// <summary>
/// Decodes a file attachment and saves it to a specified path.
/// </summary>
public class InfoPathAttachmentDecoder
{
private const int SP1Header_Size = 20;
private const int FIXED_HEADER = 16;
private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
/// <summary>
/// Accepts the Base64 encoded string
/// that is the attachment.
/// </summary>
public InfoPathAttachmentDecoder(string theBase64EncodedString)
{
byte[] theData = Convert.FromBase64String(theBase64EncodedString);
using (MemoryStream ms = new MemoryStream(theData))
{
BinaryReader theReader = new BinaryReader(ms);
DecodeAttachment(theReader);
}
}
private void DecodeAttachment(BinaryReader theReader)
{
//Position the reader to get the file size.
byte[] headerData = new byte[FIXED_HEADER];
headerData = theReader.ReadBytes(headerData.Length);
fileSize = (int)theReader.ReadUInt32();
attachmentNameLength = (int)theReader.ReadUInt32() * 2;
byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
//InfoPath uses UTF8 encoding.
Encoding enc = Encoding.Unicode;
attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);
decodedAttachment = theReader.ReadBytes(fileSize);
}
public void SaveAttachment(string saveLocation)
{
string fullFileName = saveLocation;
if (!fullFileName.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
fullFileName += Path.DirectorySeparatorChar;
}
fullFileName += attachmentName;
if (File.Exists(fullFileName))
File.Delete(fullFileName);
FileStream fs = new FileStream(fullFileName, FileMode.CreateNew);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(decodedAttachment);
bw.Close();
fs.Close();
}
public string Filename
{
get { return attachmentName; }
}
public byte[] DecodedAttachment
{
get { return decodedAttachment; }
}
It appears that your issue has to do with your use of MoveToNext. Per the documentation, this function moves to the next sibling, and does not navigate to children elements. Your code appears to go to the first element it finds (presumably my:myFields), looks for the first child named my:field2 (it only pulls the first one since you are using SelectSingleNode, and then goes to the next sibling of my:myFields (not the next sibling of my:field2). One way to fix this might be to replace your current do-while loop with a call to SelectNodes like the following and then iterate over nodeList.
XmlNodeList nodelist = ipFormNav.SelectNodes("//my:field2", nsManager);
Related
This question already has answers here:
How to read a text file reversely with iterator in C#
(11 answers)
Closed 1 year ago.
I'm trying to figure out how to either Record which line I'm in, for example, line = 32, allowing me to just add line-- in the previous record button event or find a better alternative.
I currently have my form setup and working where if I click on "Next Record" button, the file increments to the next line and displays the cells correctly within their associated textboxes, but how do I create a button that goes to the previous line in the .csv file?
StreamReader csvFile;
public GP_Appointment_Manager()
{
InitializeComponent();
}
private void buttonOpenFile_Click(object sender, EventArgs e)
{
try
{
csvFile = new StreamReader("patients_100.csv");
// Read First line and do nothing
string line;
if (ReadPatientLineFromCSV(out line))
{
// Read second line, first patient line and populate form
ReadPatientLineFromCSV(out line);
PopulateForm(line);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private bool ReadPatientLineFromCSV(out string line)
{
bool result = false;
line = "";
if ((csvFile != null) && (!csvFile.EndOfStream))
{
line = csvFile.ReadLine();
result = true;
}
else
{
MessageBox.Show("File has not been opened. Please open file before reading.");
}
return result;
}
private void PopulateForm(string patientDetails)
{
string[] patient = patientDetails.Split(',');
//Populates ID
textBoxID.Text = patient[0];
//Populates Personal
comboBoxSex.SelectedIndex = (patient[1] == "M") ? 0 : 1;
dateTimePickerDOB.Value = DateTime.Parse(patient[2]);
textBoxFirstName.Text = patient[3];
textBoxLastName.Text = patient[4];
//Populates Address
textboxAddress.Text = patient[5];
textboxCity.Text = patient[6];
textboxCounty.Text = patient[7];
textboxTelephone.Text = patient[8];
//Populates Kin
textboxNextOfKin.Text = patient[9];
textboxKinTelephone.Text = patient[10];
}
Here's the code for the "Next Record" Button
private void buttonNextRecord_Click(object sender, EventArgs e)
{
string patientInfo;
if (ReadPatientLineFromCSV(out patientInfo))
{
PopulateForm(patientInfo);
}
}
Now, this is some sort of exercise. This class uses the standard StreamReader with a couple of modification, to implement simple move-forward/step-back functionalities.
It also allows to associate an array/list of Controls with the data read from a CSV-like file format. Note that this is not a general-purpose CSV reader; it just splits a string in parts, using a separator that can be specified calling its AssociateControls() method.
The class has 3 constructors:
(1) public LineReader(string filePath)
(2) public LineReader(string filePath, bool hasHeader)
(3) public LineReader(string filePath, bool hasHeader, Encoding encoding)
The source file has no Header in the first line and the text Encoding should be auto-detected
Same, but the first line of the file contain the Header if hasHeader = true
Used to specify an Encoding, if the automatic discovery cannot identify it correctly.
The positions of the lines of text are stored in a Dictionary<long, long>, where the Key is the line number and Value is the starting position of the line.
This has some advantages: no strings are stored anywhere, the file is indexed while reading it but you could use a background task to complete the indexing (this feature is not implemented here, maybe later...).
The disadvantage is that the Dictionary takes space in memory. If the file is very large (just the number of lines counts, though), it may become a problem. To test.
A note about the Encoding:
The text encoding auto-detection is reliable enough only if the Encoding is not set to the default one (UTF-8). The code here, if you don't specify an Encoding, sets it to Encoding.ASCII. When the first line is read, the automatic feature tries to determine the actual encoding. It usually gets it right.
In the default StreamReader implementation, if we specify Encoding.UTF8 (or none, which is the same) and the text encoding is ASCII, the encoder will use the default (Encoding.UTF8) encoding, since UTF-8 maps to ASCII gracefully.
However, when this is the case, [Encoding].GetPreamble() will return the UTF-8 BOM (3 bytes), compromising the calculation of the current position in the underlying stream.
To associate controls with the data read, you just need to pass a collection of controls to the LineReader.AssociateControls() method.
This will map each control to the data field in the same position.
To skip a data field, specify null instead of a control reference.
The visual example is built using a CSV file with this structure:
(Note: this data is generated using an automated on-line tool)
seq;firstname;lastname;age;street;city;state;zip;deposit;color;date
---------------------------------------------------------------------------
1;Harriett;Gibbs;62;Segmi Center;Ebanavi;ID;57854;$4444.78;WHITE;05/15/1914
2;Oscar;McDaniel;49;Kulak Drive;Jetagoz;IL;57631;$5813.94;RED;02/11/1918
3;Winifred;Olson;29;Wahab Mill;Ucocivo;NC;46073;$2002.70;RED;08/11/2008
I skipped the seq and color fields, passing this array of Controls:
LineReader lineReader = null;
private void btnOpenFile_Click(object sender, EventArgs e)
{
string filePath = Path.Combine(Application.StartupPath, #"sample.csv");
lineReader = new LineReader(filePath, true);
string header = lineReader.HeaderLine;
Control[] controls = new[] {
null, textBox1, textBox2, textBox3, textBox4, textBox5,
textBox6, textBox9, textBox7, null, textBox8 };
lineReader.AssociateControls(controls, ";");
}
The null entries correspond to the data fields that are not considered.
Visual sample of the functionality:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
class LineReader : IDisposable
{
private StreamReader reader = null;
private Dictionary<long, long> positions;
private string m_filePath = string.Empty;
private Encoding m_encoding = null;
private IEnumerable<Control> m_controls = null;
private string m_separator = string.Empty;
private bool m_associate = false;
private long m_currentPosition = 0;
private bool m_hasHeader = false;
public LineReader(string filePath) : this(filePath, false) { }
public LineReader(string filePath, bool hasHeader) : this(filePath, hasHeader, Encoding.ASCII) { }
public LineReader(string filePath, bool hasHeader, Encoding encoding)
{
if (!File.Exists(filePath)) {
throw new FileNotFoundException($"The file specified: {filePath} was not found");
}
this.m_filePath = filePath;
m_hasHeader = hasHeader;
CurrentLineNumber = 0;
reader = new StreamReader(this.m_filePath, encoding, true);
CurrentLine = reader.ReadLine();
m_encoding = reader.CurrentEncoding;
m_currentPosition = m_encoding.GetPreamble().Length;
positions = new Dictionary<long, long>() { [0]= m_currentPosition };
if (hasHeader) { this.HeaderLine = CurrentLine = this.MoveNext(); }
}
public string HeaderLine { get; private set; }
public string CurrentLine { get; private set; }
public long CurrentLineNumber { get; private set; }
public string MoveNext()
{
string read = reader.ReadLine();
if (string.IsNullOrEmpty(read)) return this.CurrentLine;
CurrentLineNumber += 1;
if ((positions.Count - 1) < CurrentLineNumber) {
AdjustPositionToLineFeed();
positions.Add(CurrentLineNumber, m_currentPosition);
}
else {
m_currentPosition = positions[CurrentLineNumber];
}
this.CurrentLine = read;
if (m_associate) this.Associate();
return read;
}
public string MovePrevious()
{
if (CurrentLineNumber == 0 || (CurrentLineNumber == 1 && m_hasHeader)) return this.CurrentLine;
CurrentLineNumber -= 1;
m_currentPosition = positions[CurrentLineNumber];
reader.BaseStream.Position = m_currentPosition;
reader.DiscardBufferedData();
this.CurrentLine = reader.ReadLine();
if (m_associate) this.Associate();
return this.CurrentLine;
}
private void AdjustPositionToLineFeed()
{
long linePos = m_currentPosition + m_encoding.GetByteCount(this.CurrentLine);
long prevPos = reader.BaseStream.Position;
reader.BaseStream.Position = linePos;
byte[] buffer = new byte[4];
reader.BaseStream.Read(buffer, 0, buffer.Length);
char[] chars = m_encoding.GetChars(buffer).Where(c => c.Equals((char)10) || c.Equals((char)13)).ToArray();
m_currentPosition = linePos + m_encoding.GetByteCount(chars);
reader.BaseStream.Position = prevPos;
}
public void AssociateControls(IEnumerable<Control> controls, string separator)
{
m_controls = controls;
m_separator = separator;
m_associate = true;
if (!string.IsNullOrEmpty(this.CurrentLine)) Associate();
}
private void Associate()
{
string[] values = this.CurrentLine.Split(new[] { m_separator }, StringSplitOptions.None);
int associate = 0;
m_controls.ToList().ForEach(c => {
if (c != null) c.Text = values[associate];
associate += 1;
});
}
public override string ToString() =>
$"File Path: {m_filePath} Encoding: {m_encoding.BodyName} CodePage: {m_encoding.CodePage}";
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing) { reader?.Dispose(); }
}
}
General approach is the following:
Add a text file input.txt like this
line 1
line 2
line 3
and set Copy to Output Directory property to Copy if newer
Create extension methods for StreamReader
public static class StreamReaderExtensions
{
public static bool TryReadNextLine(this StreamReader reader, out string line)
{
var isAvailable = reader != null &&
!reader.EndOfStream;
line = isAvailable ? reader.ReadLine() : null;
return isAvailable;
}
public static bool TryReadPrevLine(this StreamReader reader, out string line)
{
var stream = reader.BaseStream;
var encoding = reader.CurrentEncoding;
var bom = GetBOM(encoding);
var isAvailable = reader != null &&
stream.Position > 0;
if(!isAvailable)
{
line = null;
return false;
}
var buffer = new List<byte>();
var str = string.Empty;
stream.Position++;
while (!str.StartsWith(Environment.NewLine))
{
stream.Position -= 2;
buffer.Insert(0, (byte)stream.ReadByte());
var reachedBOM = buffer.Take(bom.Length).SequenceEqual(bom);
if (reachedBOM)
buffer = buffer.Skip(bom.Length).ToList();
str = encoding.GetString(buffer.ToArray());
if (reachedBOM)
break;
}
stream.Position--;
line = str.Trim(Environment.NewLine.ToArray());
return true;
}
private static byte[] GetBOM(Encoding encoding)
{
if (encoding.Equals(Encoding.UTF7))
return new byte[] { 0x2b, 0x2f, 0x76 };
if (encoding.Equals(Encoding.UTF8))
return new byte[] { 0xef, 0xbb, 0xbf };
if (encoding.Equals(Encoding.Unicode))
return new byte[] { 0xff, 0xfe };
if (encoding.Equals(Encoding.BigEndianUnicode))
return new byte[] { 0xfe, 0xff };
if (encoding.Equals(Encoding.UTF32))
return new byte[] { 0, 0, 0xfe, 0xff };
return new byte[0];
}
}
And use it like this:
using (var reader = new StreamReader("input.txt"))
{
string na = "N/A";
string line;
for (var i = 0; i < 4; i++)
{
var isAvailable = reader.TryReadNextLine(out line);
Console.WriteLine($"Next line available: {isAvailable}. Line: {(isAvailable ? line : na)}");
}
for (var i = 0; i < 4; i++)
{
var isAvailable = reader.TryReadPrevLine(out line);
Console.WriteLine($"Prev line available: {isAvailable}. Line: {(isAvailable ? line : na)}");
}
}
The result is:
Next line available: True. Line: line 1
Next line available: True. Line: line 2
Next line available: True. Line: line 3
Next line available: False. Line: N/A
Prev line available: True. Line: line 3
Prev line available: True. Line: line 2
Prev line available: True. Line: line 1
Prev line available: False. Line: N/A
GetBOM is based on this.
Now I have successfully working code (with multiple threads) for items bulk import in IN202500 screen in Acumatica.
The problem is that I am struggling to import an image of an item and actually I don't have an image by itself but only URL link to this image.
So, my question is has anyone done this in c#?
This is my piece of code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ItemImportMultiThreaded
{
public class ItemImporter
{
private IN202500.Screen _itemsScreen;
private static object _itemsSchemaLock = new object();
private static IN202500.Content _itemsSchema;
public void Login(string url, string username, string password, string company)
{
Console.WriteLine("[{0}] Logging in to {1}...", System.Threading.Thread.CurrentThread.ManagedThreadId, url);
_itemsScreen = new IN202500.Screen();
_itemsScreen.Url = url + "/PMSDB/(W(2))/Soap/IN202500.asmx";
_itemsScreen.EnableDecompression = true;
_itemsScreen.CookieContainer = new System.Net.CookieContainer();
_itemsScreen.Timeout = 36000;
_itemsScreen.Login(username, password);
Console.WriteLine("[{0}] Logged in to {1}.", System.Threading.Thread.CurrentThread.ManagedThreadId, url);
lock (_itemsSchemaLock)
{
// Threads can share the same schema.
if (_itemsSchema == null)
{
Console.WriteLine("[{0}] Retrieving IN202500 schema...", System.Threading.Thread.CurrentThread.ManagedThreadId);
_itemsSchema = _itemsScreen.GetSchema();
if (_itemsSchema == null) throw new Exception("IN202500 GetSchema returned null. See AC-73433.");
}
}
}
public void Logout()
{
_itemsScreen.Logout();
}
public void Import(List<Item> items)
{
Console.WriteLine("[{0}] Submitting {1} items to Acumatica...", System.Threading.Thread.CurrentThread.ManagedThreadId, items.Count);
var commands = new IN202500.Command[]
{
_itemsSchema.StockItemSummary.InventoryID,
_itemsSchema.StockItemSummary.Description,
_itemsSchema.GeneralSettingsItemDefaults.ItemClass,
_itemsSchema.VendorDetails.VendorID,
_itemsSchema.VendorDetails.VendorInventoryID,
_itemsSchema.VendorDetails.ServiceCommands.NewRow,
_itemsSchema.VendorDetails.VendorID,
_itemsSchema.VendorDetails.VendorInventoryID,
_itemsSchema.VendorDetails.ServiceCommands.NewRow,
_itemsSchema.VendorDetails.VendorID,
_itemsSchema.VendorDetails.VendorInventoryID,
_itemsSchema.CrossReference.AlternateID,
_itemsSchema.CrossReference.Description,
_itemsSchema.Actions.Save
};
string[][] data = new string[items.Count][];
int count = 0;
foreach(Item item in items)
{
data[count] = new string[11];
data[count][0] = item.InventoryID;
data[count][1] = item.Description.Trim();
data[count][2] = item.ItemClassID;
data[count][3] = item.DigiKey;
data[count][4] = item.DKPN;
data[count][5] = item.Mouser;
data[count][6] = item.MouserID;
data[count][7] = item.Element14;
data[count][8] = item.Element14ID;
data[count][9] = item.AlternateID;
data[count][10] = item.Descr;
count++;
}
_itemsScreen.Import(commands, null, data, false, true, true);
Console.WriteLine("[{0}] Submitted {1} items to Acumatica.", System.Threading.Thread.CurrentThread.ManagedThreadId, items.Count);
}
}
}
I tried to use FileStream but that didn't work.
If by URL link you mean an external http resource, you can download the image and upload it.
The StockItems image field cycle through all the images contained in the Files popup in the order they are displayed:
I uploaded the images from a static external Url using the following code:
const string imageUrl = "https://cdn.acumatica.com/media/2016/03/software-technology-industries-small.jpg";
string path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetTempFileName(), ".jpg"));
// Download Image
using (WebClient client = new WebClient())
{
client.DownloadFile(new Uri(imageUrl), path);
}
// ReadUploadFile function below
byte[] data = ReadUploadFile(path);
_itemsScreen.Import(new IN202500.Command[]
{
// Get Inventory Item
new Value
{
Value = "D1",
LinkedCommand = _itemsSchema.StockItemSummary.InventoryID,
},
_itemsSchema.Actions.Save,
// Upload Inventory Item Image
new Value
{
FieldName = Path.GetFileName(path),
LinkedCommand = _itemsSchema.StockItemSummary.ServiceCommands.Attachment
},
_itemsSchema.Actions.Save
},
null,
new string[][]
{
new string[]
{
// Image data
Convert.ToBase64String(data)
}
},
false,
false,
true);
public byte[] ReadUploadFile(string filePath)
{
byte[] filedata;
using (FileStream file = File.Open(filePath,
FileMode.Open,
FileAccess.ReadWrite,
FileShare.ReadWrite))
{
filedata = new byte[file.Length];
file.Read(filedata, 0, filedata.Length);
}
if (filedata == null || filedata.Length == 0)
{
throw new Exception(string.Concat("Invalid or empty file: ", filePath));
}
return filedata;
}
You can try using the below, Tested Code.
var content = _context.CR306000GetSchema(); _context.CR306000Clear();
var commands = new List();
ReqParameter(content, ref commands);
commands.Add(content.Actions.Save);
commands.Add(content.CaseSummary.CaseID);
var orderResults = _context.CR306000Submit(commands.ToArray());
private static void ReqParameter(CR306000Content content, ref List cmds) { if (cmds == null) throw new ArgumentNullException("cmds");
private static void ReqParameter(CR306000Content content, ref List<Command> cmds)
{
if (cmds == null) throw new ArgumentNullException("cmds");
byte[] filedata= null;
Uri uri = new Uri("https://cdn.acumatica.com/media/2016/03/software-technology-industries-small.jpg"); // change the required url of the data that has to be fetched
if (uri.IsFile)
{
string filename = System.IO.Path.GetFileName(uri.LocalPath);
filedata = System.Text.Encoding.UTF8.GetBytes(uri.LocalPath);
}
if (filedata == null)
{
WebClient wc = new WebClient();
filedata = wc.DownloadData(uri);
}
cmds = new List<Command>
{
//Case Header Details
new Value { Value="<NEW>",LinkedCommand = content.CaseSummary.CaseID},
new Value { Value="L41",LinkedCommand = content.CaseSummary.ClassID},
new Value { Value="ABCSTUDIOS",LinkedCommand = content.CaseSummary.BusinessAccount, Commit = true},
new Value { Value="Test subject created from envelop call 11C",LinkedCommand = content.CaseSummary.Subject},
// body of the case
new Value{Value= "Body of the content for created through envelop call 11B", LinkedCommand = content.Details.Description},
//Attaching a file
new Value
{
Value = Convert.ToBase64String(filedata), // byte data that is passed to through envelop
FieldName = "Test.jpg",
LinkedCommand =
content.CaseSummary.ServiceCommands.Attachment
},
};
}
Let me know if this works for you.
Thanks
I am trying to fill a word document with data from an XML. I am using openXML to fill the document, which works great and save it as .docx. The thing is I have to open Word and save the document as an .odt and then use the OpenOffice SDK to open the .docx and save it as a pdf. When I don't save the .docx as .odt, the formatting is off.
What I need to be able to do is be able to either convert the .docx to .odt or save it originally as .odt.
Here is what I have right now:
static void Main()
{
string documentText;
XmlDocument xmlDoc = new XmlDocument(); // Create an XML document object
xmlDoc.Load("C:\\Cache\\MMcache.xml"); // Load the XML document from the specified file
XmlNodeList PatientFirst = xmlDoc.GetElementsByTagName("PatientFirst");
XmlNodeList PatientSignatureImg = xmlDoc.GetElementsByTagName("PatientSignatureImg");
byte[] byteArray = File.ReadAllBytes("C:\\Cache\\TransportationRunReporttemplate.docx");
using (MemoryStream stream = new MemoryStream())
{
stream.Write(byteArray, 0, (int)byteArray.Length);
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(stream, true))
{
using (StreamReader reader = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
{
documentText = reader.ReadToEnd();
}
using (StreamWriter writer = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create)))
{
writer.Write(documentText);
}
}
// Save the file with the new name
File.WriteAllBytes("C:\\Cache\\MYFINISHEDTEMPLATE.docx", stream.ToArray());
}
}
private static void AddPicture(Bitmap bitmap)
{
using (WordprocessingDocument doc = WordprocessingDocument.Open("C:\\Cache\\MYFINISHEDTEMPLATE.docx", true))
{
//Bitmap image = new Bitmap("C:\\Cache\\scribus.jpg");
SdtElement controlBlock = doc.MainDocumentPart.Document.Body
.Descendants<SdtElement>()
.Where
(r =>
r.SdtProperties.GetFirstChild<Tag>().Val == "Signature"
).SingleOrDefault();
// Find the Blip element of the content control.
A.Blip blip = controlBlock.Descendants<A.Blip>().FirstOrDefault();
ImagePart imagePart = doc.MainDocumentPart
.AddImagePart(ImagePartType.Jpeg);
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Jpeg);
stream.Position = 0;
imagePart.FeedData(stream);
}
blip.Embed = doc.MainDocumentPart.GetIdOfPart(imagePart);
/* DW.Inline inline = controlBlock
.Descendants<DW.Inline>().FirstOrDefault();
// 9525 = pixels to points
inline.Extent.Cy = image.Size.Height * 9525;
inline.Extent.Cx = image.Size.Width * 9525;
PIC.Picture pic = inline
.Descendants<PIC.Picture>().FirstOrDefault();
pic.ShapeProperties.Transform2D.Extents.Cy
= image.Size.Height * 9525;
pic.ShapeProperties.Transform2D.Extents.Cx
= image.Size.Width * 9525;*/
}
ConvertToPDF(#"C:\Cache\MYFINISHEDTEMPLATE2.docx",#"C:\Cache\OpenPdf.pdf");
}
public static Bitmap Base64StringToBitmap(string base64String)
{
Bitmap bmpReturn = null;
byte[] byteBuffer = Convert.FromBase64String(base64String);
MemoryStream memoryStream = new MemoryStream(byteBuffer);
memoryStream.Position = 0;
bmpReturn = (Bitmap)Bitmap.FromStream(memoryStream);
memoryStream.Close();
memoryStream = null;
byteBuffer = null;
return bmpReturn;
}
public static void ConvertToPDF(string inputFile, string outputFile)
{
if (ConvertExtensionToFilterType(System.IO.Path.GetExtension(inputFile)) == null)
throw new InvalidProgramException("Unknown file type for OpenOffice. File = " + inputFile);
StartOpenOffice();
//Get a ComponentContext
var xLocalContext =
Bootstrap.bootstrap();
//Get MultiServiceFactory
var xRemoteFactory =
(XMultiServiceFactory)
xLocalContext.getServiceManager();
//Get a CompontLoader
var aLoader =
(XComponentLoader)xRemoteFactory.createInstance("com.sun.star.frame.Desktop");
//Load the sourcefile
XComponent xComponent = null;
try
{
xComponent = InitDocument(aLoader,
PathConverter(inputFile), "_blank");
//Wait for loading
while (xComponent == null)
{
Thread.Sleep(1000);
}
// save/export the document
SaveDocument(xComponent, inputFile, PathConverter(outputFile));
}
finally
{
if (xComponent != null) xComponent.dispose();
}
}
private static void StartOpenOffice()
{
var ps = Process.GetProcessesByName("soffice.exe");
if (ps.Length != 0)
throw new InvalidProgramException("OpenOffice not found. Is OpenOffice installed?");
if (ps.Length > 0)
return;
var p = new Process
{
StartInfo =
{
Arguments = "-headless -nofirststartwizard",
FileName = "soffice.exe",
CreateNoWindow = true
}
};
var result = p.Start();
if (result == false)
throw new InvalidProgramException("OpenOffice failed to start.");
}
private static XComponent InitDocument(XComponentLoader aLoader, string file, string target)
{
var openProps = new PropertyValue[1];
openProps[0] = new PropertyValue { Name = "Hidden", Value = new Any(true) };
XComponent xComponent = aLoader.loadComponentFromURL(
file, target, 0,
openProps);
return xComponent;
}
private static void SaveDocument(XComponent xComponent, string sourceFile, string destinationFile)
{
var propertyValues = new PropertyValue[2];
// Setting the flag for overwriting
propertyValues[1] = new PropertyValue { Name = "Overwrite", Value = new Any(true) };
//// Setting the filter name
propertyValues[0] = new PropertyValue
{
Name = "FilterName",
Value = new Any(ConvertExtensionToFilterType(System.IO.Path.GetExtension(sourceFile)))
};
((XStorable)xComponent).storeToURL(destinationFile, propertyValues);
}
private static string PathConverter(string file)
{
if (file == null || file.Length == 0)
throw new NullReferenceException("Null or empty path passed to OpenOffice");
return String.Format("file:///{0}", file.Replace(#"\", "/"));
}
public static string ConvertExtensionToFilterType(string extension)
{
switch (extension)
{
case ".odt":
case ".doc":
case ".docx":
case ".txt":
case ".rtf":
case ".html":
case ".htm":
case ".xml":
case ".wps":
case ".wpd":
return "writer_pdf_Export";
case ".xls":
case ".xlsb":
case ".ods":
return "calc_pdf_Export";
case ".ppt":
case ".pptx":
case ".odp":
return "impress_pdf_Export";
default: return null;
}
}
}
}
Just for information I cannot use anything that uses Interop because word will not be installed the machine. I am using OpenXML and OpenOffice
This is what I would try (details below):
1) try Doc format instead of DocX
2) switch to Libre Office and try DocX again
2) use the odf-converter to get a better DocX -> ODT conversion.
More details...
There's something called the odf-conveter (opensource) that can convert DocX->ODT which gives you (typically) more accurate DocX->ODT than Open Office. Take a look at odf-conveter-integrator by OONinja for pre-packaged versions.
Also, Libre Office has DocX support ahead of OpenOffice so you might get a better result simply by switching to Libre Office.
A further option is to start from Doc format rather than DocX. In the OpenOffice world that translates much better to ODT and then to PDF.
Hope that helps.
You may try to use Docxpresso to generate your .odt directly from HTML + CSS code and avoid any conversion issue.
Docxpresso is free for non-commercial use.
I'm trying to implement a handler in my WPF application for files dragged from a zip archive. The handler should get the file content for further processing.
My environment: Windows7, 7-zip installed, Visual Studio 2012 Express, .Net 4.5
Here is the code of a simple MainWindow app to demonstrate the problem:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AllowDrop= true;
Drop += onDrop;
}
private void onDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("FileContents"))
{
var fileContents = e.Data.GetData("FileContents");
//get file contents...
}
}
}
When I drag a file contained in a zip archive to my Window the call to e.Data.GetData("FileContents") throws a System.ArgumentException ("Argument out of Range") with the following callstack:
System.Windows.DataObject.OleConverter.GetDataInner(formatetc, medium)
System.Windows.DataObject.OleConverter.GetDataFromOleHGLOBAL(format, aspect, index)
System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(format, aspect, index)
System.Windows.DataObject.OleConverter.GetData(format, autoConvert, aspect, index)
System.Windows.DataObject.OleConverter.GetData(format, autoConvert)
System.Windows.DataObject.GetData(format, autoConvert)
System.Windows.DataObject.GetData(format)
TestZip.MainWindow.onDrop(sender, e) Zeile 34 C#
I've looked up the source code of this OleConverter (http://reflector.webtropy.com/default.aspx/Dotnetfx_Win7_3#5#1/Dotnetfx_Win7_3#5#1/3#5#1/DEVDIV/depot/DevDiv/releases/Orcas/NetFXw7/wpf/src/Core/CSharp/System/Windows/dataobject#cs/1/dataobject#cs) but the GetDataInner() method is implemented like
private void GetDataInner(ref FORMATETC formatetc, out STGMEDIUM medium)
{
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); // BlessedAssert
try
{
_innerData.GetData(ref formatetc, out medium);
}
finally
{
SecurityPermission.RevertAssert();
}
}
So this does also not provide further info of what's wrong here.
I also tried with uninstalled 7-zip and with different zip archives, but no change.
My question: Does any one have a clue what's going wrong here? What do I need to do in order to get the content of a file from a zip-archive dropped onto my window?
Old question but I needed to do this today so....
Using statements:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
Reference PresentationCore
https://msdn.microsoft.com/en-us/library/system.windows.idataobject(v=vs.110).aspx
First you need to get the "FileDescriptor" contents, here's a class which reads them.
/// <summary>
/// Specifies which fields are valid in a FileDescriptor Structure
/// </summary>
[Flags]
enum FileDescriptorFlags : uint
{
ClsId = 0x00000001,
SizePoint = 0x00000002,
Attributes = 0x00000004,
CreateTime = 0x00000008,
AccessTime = 0x00000010,
WritesTime = 0x00000020,
FileSize = 0x00000040,
ProgressUI = 0x00004000,
LinkUI = 0x00008000,
Unicode = 0x80000000,
}
internal static class FileDescriptorReader
{
internal sealed class FileDescriptor
{
public FileDescriptorFlags Flags{get;set;}
public Guid ClassId{get;set;}
public Size Size{get;set;}
public Point Point{get;set;}
public FileAttributes FileAttributes{get;set;}
public DateTime CreationTime{get;set;}
public DateTime LastAccessTime{get;set;}
public DateTime LastWriteTime{get;set;}
public Int64 FileSize{get;set;}
public string FileName{get;set;}
public FileDescriptor(BinaryReader reader)
{
//Flags
Flags = (FileDescriptorFlags)reader.ReadUInt32();
//ClassID
ClassId = new Guid(reader.ReadBytes(16));
//Size
Size = new Size(reader.ReadInt32(), reader.ReadInt32());
//Point
Point = new Point(reader.ReadInt32(), reader.ReadInt32());
//FileAttributes
FileAttributes = (FileAttributes)reader.ReadUInt32();
//CreationTime
CreationTime = new DateTime(1601,1,1).AddTicks(reader.ReadInt64());
//LastAccessTime
LastAccessTime = new DateTime(1601,1,1).AddTicks(reader.ReadInt64());
//LastWriteTime
LastWriteTime = new DateTime(1601, 1, 1).AddTicks(reader.ReadInt64());
//FileSize
FileSize = reader.ReadInt64();
//FileName
byte[] nameBytes = reader.ReadBytes(520);
int i = 0;
while(i < nameBytes.Length)
{
if (nameBytes[i] == 0 && nameBytes[i + 1] == 0)
break;
i++;
i++;
}
FileName = UnicodeEncoding.Unicode.GetString(nameBytes, 0, i);
}
}
public static IEnumerable<FileDescriptor> Read(Stream fileDescriptorStream)
{
BinaryReader reader = new BinaryReader(fileDescriptorStream);
var count = reader.ReadUInt32();
while (count > 0)
{
FileDescriptor descriptor = new FileDescriptor(reader);
yield return descriptor;
count--;
}
}
public static IEnumerable<string> ReadFileNames(Stream fileDescriptorStream)
{
BinaryReader reader = new BinaryReader(fileDescriptorStream);
var count = reader.ReadUInt32();
while(count > 0)
{
FileDescriptor descriptor = new FileDescriptor(reader);
yield return descriptor.FileName;
count--;
}
}
}
Now using that you can get the matching file content for each file:
static class ClipboardHelper
{
internal static MemoryStream GetFileContents(System.Windows.IDataObject dataObject, int index)
{
//cast the default IDataObject to a com IDataObject
IDataObject comDataObject;
comDataObject = (IDataObject)dataObject;
System.Windows.DataFormat Format = System.Windows.DataFormats.GetDataFormat("FileContents");
if (Format == null)
return null;
FORMATETC formatetc = new FORMATETC();
formatetc.cfFormat = (short)Format.Id;
formatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;
formatetc.lindex = index;
formatetc.tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_HGLOBAL;
//create STGMEDIUM to output request results into
STGMEDIUM medium = new STGMEDIUM();
//using the com IDataObject interface get the data using the defined FORMATETC
comDataObject.GetData(ref formatetc, out medium);
switch (medium.tymed)
{
case TYMED.TYMED_ISTREAM: return GetIStream(medium);
default: throw new NotSupportedException();
}
}
private static MemoryStream GetIStream(STGMEDIUM medium)
{
//marshal the returned pointer to a IStream object
IStream iStream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
Marshal.Release(medium.unionmember);
//get the STATSTG of the IStream to determine how many bytes are in it
var iStreamStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
iStream.Stat(out iStreamStat, 0);
int iStreamSize = (int)iStreamStat.cbSize;
//read the data from the IStream into a managed byte array
byte[] iStreamContent = new byte[iStreamSize];
iStream.Read(iStreamContent, iStreamContent.Length, IntPtr.Zero);
//wrapped the managed byte array into a memory stream
return new MemoryStream(iStreamContent);
}
}
Now you can enumerate the streams in the file contents:
var fileDescriptor = (MemoryStream)Clipboard.GetDataObject().GetData("FileGroupDescriptorW");
var files = FileDescriptorReader.Read(fileDescriptor);
var fileIndex = 0;
foreach (var fileContentFile in files)
{
if ((fileContentFile.FileAttributes & FileAttributes.Directory) != 0)
{
//Do something with directories?
//Note that directories do not have FileContents
//And will throw if we try to read them
}
else
{
var fileData = ClipboardHelper.GetFileContents(Clipboard.GetDataObject(), FileIndex);
fileData.Position = 0;
//Do something with the fileContent Stream
}
fileIndex++;
}
Although I'm new to the Open-XML world, I've already encountered some troubles/problems using it. The most of them were easily solved but I can't get around this one:
public class ReportDocument : IDisposable
{
private MemoryStream stream;
private WordprocessingDocument document;
private MainDocumentPart mainPart;
public byte[] DocumentData
{
get
{
this.document.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument);
byte[] documentData = this.stream.ToArray();
return documentData;
}
}
public ReportDocument()
{
byte[] template = DocumentTemplates.SingleReportTemplate;
this.stream = new MemoryStream();
stream.Write(template, 0, template.Length);
this.document = WordprocessingDocument.Open(stream, true);
this.mainPart = document.MainDocumentPart;
}
public void SetReport(Report report)
{
Body body = mainPart.Document.Body;
var placeholder = body.Descendants<SdtBlock>();
this.SetPlaceholderTextValue(placeholder, "Company", WebApplication.Service.Properties.Settings.Default.CompanyName);
this.SetPlaceholderTextValue(placeholder, "Title", String.Format("Status Report for {0} to {1}", report.StartDate.ToShortDateString(),
report.ReportingInterval.EndDate.ToShortDateString()));
//this.SetPlaceholderTextValue(placeholder, "Subtitle", String.Format("for {0}", report.ReportingInterval.Project.Name));
this.SetPlaceholderTextValue(placeholder, "Author", report.TeamMember.User.Username);
this.SetPlaceholderTextValue(placeholder, "Date", String.Format("for {0}", DateTime.Today.ToShortDateString()));
}
private void SetPlaceholderTextValue(IEnumerable<SdtBlock> sdts, string alias, string value)
{
SdtContentBlock contentBlock = this.GetContentBlock(sdts, alias);
Text text = contentBlock.Descendants<Text>().First();
text.Text = value;
}
private SdtContentBlock GetContentBlock(IEnumerable<SdtBlock> sdts, string alias)
{
return sdts.First(sdt => sdt.Descendants<SdtAlias>().First().Val.Value == alias).SdtContentBlock;
}
public void Dispose()
{
this.document.Close();
}
}
So i create a new document, based on a template it gains through the memory stream and want to write it back to a memory stream when the changes are made.
The big problem is, when i save the resulting byte array the data docx file is corrupted:
The document.xml in .\word is called document2.xml
The document.xml.rels in .\word_rels is called document2.xml.rels and it contains
I hope some of you can provide good solutions for it.
MFG SakeSushiBig
Change your DocumentData property to this and I think it should work. The important thing is to close the document before you read the memorystream.
public byte[] DocumentData
{
get
{
this.document.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument);
this.document.MainDocumentPart.Document.Save();
this.document.Close();
byte[] documentData = this.stream.ToArray();
return documentData;
}
}