Save RichTextBox content containing both images and custom token elements - c#

Struggling with how to successfully save and load my System.Windows.Controls.RichTextBox content containing all of the following: formatted text, images, custom type-defined token elements, custom dynamic token elements.
By token elements I mean my custom classes inheriting from System.Windows.Documents.Run where type-defined is such that does not need to remember any dynamically set property values (since action is taken based on the type which needs to be remembered after loading) and dynamic ones are such that need to also retain dynamically set properties (action is taken not only based on type, but also based on these set values).
I know of the following 3 methods to save/load, neither of which is sufficient:
1)
string xamlStream = System.Windows.Markup.XamlWriter.Save(myRichTxtBx.Document);
and then saving the string
2)
TextRange content = new TextRange(myRichTxtBx.Document.ContentStart, myRichTxtBx.Document.ContentEnd);
content.Save(myFileStream, DataFormats.XamlPackage, true);
3)
TextRange content = new TextRange(myRichTxtBx.Document.ContentStart, myRichTxtBx.Document.ContentEnd);
content.Save(myFileStream, DataFormats.Xaml, true);
These are the problems with those:
1) unable to load image after restarting the application (but remembers properties)
2) does not remember the properties (but is able to load image after restarting the app)
3) won't load image not even in the same instance of the app and also does not remember the property values
I could only find answers resolving image saving issues (2) or property issues (1), but never both.
The goal is to have a tokenizable RichTextBox, where tokens are either replaced by values from database based on provided ORM object (= type-defined token) or by dynamically set values by user based again on a provided ORM object.

I have solved the issue by a very ugly workaround:
To save the document I use the method (1) described above. Before this, I traverse the FlowDocument by a custom walker and replace each image element with a custom inline token element (much like the other tokens). A hash ID is assigned as a property to this substitute element and the image itself is saved having the hash as its file name (serves to identify token with the image file). Images, along with the main document (saved by method (1)), are packaged into a single file.
When loading everything back, package is unpacked, document loaded keeping the tokens with their properties and substitute image elements are replaced by the actual images from the files saved in the package using once again the before-mentioned custom walker and the established hash token-file relation.

Related

Write hidden information (like software name & version) in Excel file using OpenXML

My c# software exports to Excel files using OpenXML. I am reading and editing these files in the software even after user edits them in Excel. I want to add the software name and version somewhere, so, when the software opens the file, recognize it. Is there any property in the Workbook as document information, comments, or only visible to OpenXML not Excel?
For now the best workaround is to save this information in an excel sheet and hide the sheet. The point is this information should not be accidentally deleted by the user.
I'd use a Custom XML Part.
The content of a Custom XML Part is only accessible through the object model or by unzipping the file "package". So the information can't be altered accidentally or even viewed without extra effort. And if you're processing the file via Open XML, accessing a Custom XML Part is reasonably straight-forward.
(The user can see Document Properties (suggested in a comment) via the UI, in the File/Info tab.)
Set a custom property for excel document via Openxml
Find method SetCustomProperty() at the bottom of page. This function is written for Word document so change open-file line to below one for Excel document
using (var document = SpreadsheetDocument.Open(fileName, true))
And you are good to add any property to your file.
How to Hide properties
The properties will be visible in Excel through File-> Info -> Properties -> Advanced Properties window. Users will be able to delete them there. If this is not desired, a property won't be visible in excel if instead of this unique id
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
another one is used:
newProp.FormatId = Guid.NewGuid().ToString("B");
Note: for saving string use VTLPWSTR type. Do not use type VTBString along with the unique ID given above as Excel automatically deletes your property when you edit it in there (just by experience, I don't know why!).
How to read the properties?
You saved your file. Then, open it again and loop over all properties
foreach (CustomDocumentProperty property in document.CustomFilePropertiesPart.Properties)
{
if (property.Name.Value == nameof(Product) &&
property.VTBString.Text == Product)
return true;
}
where Product is string property holds the name of software, and VTBString is used to save value of Product. As many properties as desired can be saved and read with this method.

Umbraco multilanguage with URL change

I am working with Umbraco v7.x. I have few static pages and they need to be added in two languages(en/da).
I know there are two ways to translate
1- Copy folder and assign different culture and hostname and add fields data according to language.
2 - Use dictionary items.
But my problem is customer wants to have custom fields on all pages so he can change static page data without having the need to ask developer. So if I use first method to change language that would also change URL which is not required for this solution.
Second I use dictionary than how can customer can change field data because he had to go to dictionary items and make any change there. This is not a problem but text needs to be formatted and this is not possible if I use dictionary items.
Any work around to this problem.
Thanks
I recommend using Vorto if you want a 1:1 translated site (meaning each piece of content has a translation for each language. Use dictionary items for text that was hard-coded into your template but Vorto will wrap your property editors so that you can edit each language in the same node. You can then use HasVortoValue() and GetVortoValue() instead of HasValue() and GetPropertyValue() methods that come with Umbraco. This will return the correct value based on the culture of the request. You will also need to configure Umbraco to load the multilingual content by setting a host name and associate that with a culture. You do that by selecting "Culture and Hostnames" in the contextual menu for the home node and and click "Add Domain" (you will need to have first added the language in the Settings section):
Alternatively, if you want to use a subfolder for each language instead of a differeent domain (e.g. sitename.com/english instead of english.sitename.com) you can create a custom Content Finder. I have written a couple blog posts on how to do that here and here.

Read/Write SharePoint User Field [Word 2010 VSTO]

I've been experimenting with reading SharePoint 2013 Site Column metadata from within a Word 2010 Application-level C# VSTO.
For testing I've set-up Site Columns for every type that SharePoint uses, then created a Document Content Type that ties to them all -- thus all these columns are embedded into the Word document (looks to be stored within customXml within the document file).
By reading from the _Document.ContentTypeProperties property within the VSTO's code, I can access most types, but I'm having difficulty accessing a 'Person or Group' Site Column's data -- I'm getting COM Exceptions attempting to read or write to an item's .Value property.
By looking at the XSD schema in customXml, I can see a single-value User column is made up of three values: DisplayName (type string), AccountType (type string) and AccountId (type UserId) -- however I don't see a way to read/write from/to this within the VSTO? Multi-value User columns appear to be completely different, and are made up of two string values: an ID (appears to be the SharePoint user's ID) and a string-based ID (or at least that's what I think the i:0#.w|domain\userid is, anyway).
Word itself can edit both single- and multi-valued User column data via the Document Panel, but only if Word is currently connected to SharePoint -- otherwise the functionality is disabled. I'd assume the same would be true for the VSTO, if I could access the values at all...
My two questions are:
Is there a way to read/write single- and multi-value User fields from within VSTO code (even if it's not via the _Document.ContentTypeProperties property)?
Is there a way to do Q1 when if not connected to SharePoint (if, say, the values are known to the code)?
(I've been somewhat overly verbose in case my workings so far are useful to someone else even if I get no answers; there doesn't seem to be a great amount of information about this anywhere)
With some provisos, I believe you can do read/update these fields using VSTO - although I haven't actually created a working example using VSTO, the same objects as I'd use in Word VBA are available - the code snippets below are VBA.
The person/group values that are displayed in the DIP are stored in a Custom XML Part, even when the SharePoint server is unavailable. So the problem is not modifying the values - it's a CRUD operation, in essence - but knowing what values you can use, particularly in the multi-valued case. If you know how to construct valid values (let's say you have an independent list of email addresses) then you can make the modifications locally. Personally, I don't know how I would construct a valid value for the multi-valued case so I'd basically have to contact the server.
So assuming you have the data you need to update locally...
When SharePoint serves a Word Document, it inserts/updates several Custom XML Parts. One contains a set of schemas (as you have discovered). Another contains the data. All you really need to do is access the correct Custom XML Part, find the XML Element corresponding to your SharePoint user/group column, then it's a CRUD operation on the subElements of that Element.
You can find the correct Custom XML Part using the appropriate namespace name, e.g.
Const metaPropDataUri as String = _
"http://schemas.microsoft.com/office/2006/metadata/properties"
Dim theDoc as Word.Document
Dim cxp as Office.CustomXMLPart
Dim cxps as Office.CustomXMLParts
Set theDoc = ActiveDocument
Set cxps = theDoc.CustomXMLParts.SelectByNamespace(metaPropDataUri)
If there is more than one part associate with that Namespace, I don't know for sure how to choose the correct one. AFAIK Word/Sharepoint only ever creates one, and experiments suggest that if there is another one, SharePoint works with the first one. So I use
Set cxp = cxps(1)
At this point you need to know the XML Element name of the person/group column. It may be the same as the external name (the one you can see in the SharePoint list), but if for example someone called the Sharepoint column "person group", the Element name will be "person_x0020_group". If the name isn't going to vary, you can get it from the schema XML as a one-off task. Or it may be easy to generate the correct element name from any given SharePoint name. Otherwise, you can get it dynamically from the Schema XML, which you can get (as a string) using
theDoc.ContentTypeProperties.SchemaXML
What you need to do then is find the element with attribute ma:displayName="the external name" and get the value of the name attribute. I would imagine that's quite straightforward using c#, a suitable XML object, and a bit of XPath, say
//xsd:element[#ma:displayName='person group'][1]/#name
which should return 'person_x0020_group'
You can then get the Element node for your data, e.g. something along the lines of
Dim cxn As Office.CustomXMLNode
Set cxn = cxp.SelectSingleNode("//*[name()='person_x0020_group'][1]")
Or you may find it's preferable to get the namespace Uri of the Elements in this Custom XML Part and use that to help you locate the correct node. The name is a long hex string assigned by SharePoint. You can get it from the Schema XML using, e.g.
//xsd:schema[1]/#targetNamespace
Once you have your node, you would use the known structures (i.e. the ones you have found in the Schemas) to get/modify/create child nodes as required.
of course you can. You should use the SharePoint Client-side Object model (CSOM) to manipulate SharePoint data from a location away from the server. The only thing you will need is the URL of your SharePoint site.
You can then connect through CSOM like this:
ClientContext context = new ClientContext("SITEURL");
Site site = context.Site;
Web web = context.Web;
context.Load(site);
context.Load(web);
context.ExecuteQuery();
See here an example to set a single user field:
First get the ID of the user through ensuring the username
u = context.Web.EnsureUser(UserOrGroupName);
context.Load(u);
context.ExecuteQuery();
To set the value, you can use this string format:
userid;#userloginname;#
To set the field use this:
item[myusercolumn] = "userid;#userloginname;#";
item.Update();
context.ExecuteQuery();
To set a multi user field, you can use the same code, just use ;# to concat the different usernames, such as:
item[myusercolumn] = "userid1;#userloginname1;#userid2;#userloginname2;#userid3;#userloginname3;#";
item.Update();
context.ExecuteQuery();
Hope this helps

SSIS - How do I load data from text files where the path of files is inside another text file?

I have a text file that contains a list of files to load into database.
The list contains two columns:
FilePath,Type
c:\f1.txt,A
c:\f2.txt,B
c:\f3.txt,B
I want to provide this file as the source to SSIS. I then want it to go through it line by line. For each line, I want it to read the file in the FilePath column and check the Type.
If type is A then I want it to ignore the first 4 lines of the file that is located at the FilePath column of the current line and then load rest of the data inside that file in a table.
If type is B then I want it to open the file and copy first column of the file into table 1 and second column into table 2 for all of the lines.
I would really appreciate if someone can please provide me a high level list of steps I need to follow.
Any help is appreciated.
Here is one way of doing it within SSIS. Below steps are with respect to SSIS 2008 R2.
Create an SSIS package and create three package variables namely FileName, FilesToRead and Type. FilesToRead variable will hold the list of files and their types information. We will have a loop that will go through each of those records and store the information in FileName and Type variables every time it loops through.
On the control flow tab, place a Data flow task followed by a ForEach Loop container. The data flow task would read the file containing the list of files that has to be processed. The loop would then go through each file. Your control flow tab would finally look something like this. For now, there will be errors because nothing is configured. We will get to that shortly.
On the connection manager section, you need four connections.
First, you need an OLE DB connection to connect to the database. Name this as SQLServer.
Second, a flat file connection manager to read the file that contains the list of files and types. This flat file connection manager will contain two columns configured namely FileName and Type Name this as Files.
Third, another flat file connection manager to read all files of type A. Name this as Type_A. In this flat file connection manager, enter the value 4 in the text box Header rows to skip so that the first four rows are always skipped.
Fourth, one more flat file connection manager to read all files of type B. Name this as Type_B.
Let's get back to control flow. Double-click on the first data flow task. Inside the data flow task, place a flat file source that would read all the files using the connection manager Files and then place a Recordset Destination. Configure the variable FilesToRead in the recordset destination. Your first data flow task would like as shown below.
Now, let's go back to control flow tab again. Configure the ForEach loop as shown below. This loop will go through the recordset stored in the variable FilesToRead. Since, the recordset contains two columns, each time a record is looped through, the variables FileName and Type will be assigned the value of the current record.
Inside, the for each loop container, there are two data flow tasks namely Type A files and Type B files. You can configure each of these data flow tasks according to your requirements to read the files from connection managers. However, we need to disable the tasks based on the file that is being read.,
Type A files data flow task should be enabled only when A type files are being processed.
Similarly, Type B files data flow task should be enabled only when B type files are being processed.
To achieve this, click on the Type A files data flow task and press F4 to bring the properties. Click on the Ellipsis button available on the Expression property.
On the Property Expressions Editor, select Disable Property and enter the expression !(#[User::Type] == "A")
Similarly, click on the Type B files data flow task and press F4 to bring the properties. Click on the Ellipsis button available on the Expression property.
On the Property Expressions Editor, select Disable Property and enter the expression !(#[User::Type] == "B")
Here is a sample Files.txt containing only A type file in the list. When the package is executed to read this file, you will notice that only the Type A files data flow task.
Here is another sample Files.txt containing only B type files in the list. When the package is executed to read this file, you will notice that only the Type B files data flow task.
If Files.txt contains both A and B type files, the loop will execute the appropriate data flow task based on the type of file that is being processed.
Configuring Data Flow task Type A files
Let's assume that your flat files of type A have three column layout like as shown below with comma separated values. The file data here is shown using Notepad++ with all special characters. CR LF denotes that the lines are ending with Carriage return and Line Feed. This file is stored in the path C:\f1.txt
We need a table in the database to import the data. Let's create a table named dbo.Table_A in the SQL Server database as shown here.
Now, go to the SSIS package. Here are the details to configure the Flat File connection manager named Type_A. Give a name to the connection manager. You need specify the value 4 in the Header rows to skip textbox. Your flat file connection manager should look something like this.
On the Advanced tab, you can rename the column names if you would like to.
Now that the connection manager is configured, we need to configure data flow task Type A files to process the corresponding files. Double-click on the data flow task Type A files. Place a Flat file source and OLE DB Destination inside the task.
The flat file source has to be configured to read the files from flat file connection manager.
The data flow task doesn't do anything special. It simply reads the flat files of type A and inserts the data into the table dbo.Table_A. Now, we need to configure the OLE DB Destination to insert the data into database. The column names configured in the flat file connection manager and the table are not same. So, they have to be mapped manually.
Now, that the data flow task is configured. We have to make that the file path being read from the Files.txt is passed correctly. To do this, click on the Type_A flat file connection manager and press F4 to bring the properties. Set the DelayValidation property to True. Click on the Ellipsis button on the Expressions property.
On the Property Expression builder, select ConnectionString property and set it to the Expression #[User::FileName]
Here is a sample Files.txt file containing Type A files only.
Here are the sample type A files f01.txt and f02.txt
After the package execution, following data will be found in the table Table_A
Above mentioned configuration steps have to be followed for Type B files. However, the data flow task would look slightly different since the file processing logic is different. Data flow task Type B files would something like this. Since you have to insert the two columns in type B files into different tables. You have to use Multicast transformation that would create clones of the input data. You could use each of the multicast output to pass through to a different transformation or destination.
Hope that helps you to achieve your task.
I would recommend that you create a SSIS package for each different type of file load you're going to do. You can execute those packages from another program, see here: How to execute an SSIS package from .NET?
Given this information, you can write a quick program to execute the relevant packages:
var jobs = File.ReadLines("C:\\temp\\order.txt")
.Skip(1)
.Select(line => line.Split(','))
.Select(tokens => new { File = tokens[0], Category = tokens[1] });
foreach (var job in jobs)
{
// execute the relevant package for job.Category using job.File
}
My solution would look like N + 1 flat file Connection Managers to handle the source files. CM A would address the skip first 4 rows file format, B sounds like it's just a 2 column file, etc. The last CM would be used to parse the command file you've illustrated.
Now that you have all of those Connection Managers defined, you can go about the processing logic.
Create 3 variables. 2 of type string (CurrentPath, CurrentType). 1 is of type Object and I called it Recordset.
The first Data Flow reads all the rows from the flat file source using "CM Control." This is the data you supplied in your example.
We will then use that Recordset object as the source for a ForEach Loop Container in what is commonly referred to as shredding. Bingle the term "Shred recordset ssis" and you're bound to hit a number of articles describing how to do it. The net result is that for each row in that source CM Control file, you will assign those values into the CurrentPath, CurrentType variables.
Inside that Loop container, create a central point for control for control to radiate out. I find a script task works wonderfully for this. Drag it onto the canvas, give it a strong name to indicate it's not used for anything and then create a data flow to handle each processing permutation.
The magic comes from using Expressions. Dang near everything in SSIS can have expressions set on their properties which is what separates the professionals from the poseurs. Here, we will double click on the line connecting to a given data flow and change the constraint type from "Constraint" to "Expression and Constraint" The Expression you would then use is something like #[User::CurrentType] == "A" This will ensure that path is only taken when both the parent task Succeeded and the condition is true.
The second bit of expression magic will be applied to the connection managers themselves. They will need to have their ConnectionString property driven by the value of the #[User::CurrentFile] property. This will allow a design-time value of C:\filea.txt but would allow a runtime value, from the control file, to be \\network\share\ClientFileA.txt Unless all the files have the same structure, you'll most likely need to set DelayValidation to True in the properties. Otherwise, SSIS will fail PreValidation as all the "CM A" to "CM N" would be using that CurrentFile variable which may or may not be a valid connection string for that file layout.

XML tag name being overwritten with a type defined

We are communicating with a 3rd party service using via an XML file based on standards that this 3rd party developed. They give us an XML template for each "transaction" and we read it into a DataSet using System.Data.DataSet.ReadXML, set the proper values in the DataSet, and then write the XML back using System.Data.DataSet.WriteXML. This process has worked for several different files. However, I am now adding an additional file which requires that an integer data type be set on one of the fields. Here is a scaled down version:
<EngineDocList>
<DocVersion>1.0</DocVersion>
<EngineDoc>
<MyData>
<FieldA></FieldA>
<FieldB></FieldB>
<ClientID DataType="S32"></ClientID>
</MyData>
</EngineDoc>
</EngineDocList>
When I look at the DataSet created by my call to ReadXML to this file, the MyData table has columns of FieldA, FieldB, and MyData_ID. If I then set the value of MyData_ID and then make the call to WriteXML, the export XML has no value for ClientID. Once again, if I take a way the DataType, then I do have a ClientID column, I can set it properly, and the exported XML has the proper value. However, the third party requires that this data type be defined.
Any thoughts on why the ReadXML would be renaming this tag or how I could otherwise get this process to work? Alternatively, I could revamp the way we are reading and writing the XML, but would obviously rather not go down this path although these suggestions would also be welcome. Thanks.
I would not do this with a DataSet. It has a specific focus on simulating a relational model. Much XML will not follow that model. When the DataSet sees things that don't match it's idea of the world, it either ignores them or changes them. In neither case is it a good thing.
I'd use an XmlDocument for this.

Categories