How to remove a line with a particular content in docx4j - docx4j

I want to delete a particular line in the docx if it has a particular word, say "killer".
How i can write a program using docx4j?
If i replace it with empty data, the line will be still there. I want to remove the whole line.
I tried something like this,
private void replacePlaceholders(WordprocessingMLPackage targetDocument,
String nameOfTheInvitedGuest) throws JAXBException {
List<Object> texts = targetDocument.getMainDocumentPart()
.getJAXBNodesViaXPath(XPATH_TO_SELECT_TEXT_NODES, true);
System.out.println(texts.size());
Iterator<Object> itr = texts.iterator();
while (itr.hasNext()) {
Object obj = itr.next();
Text text = (Text) ((JAXBElement) obj).getValue();
// System.out.println(text.getValue());
if (text.getValue().contains("Hulk Hogan")) {
itr.remove();
}
else {
String textValue = replacePlaceholderOfInvitedGuestWithGivenName(
nameOfTheInvitedGuest, text.getValue());
for (Object key : templateProperties.keySet()) {
textValue = textValue.replaceAll("\\$\\{" + key + "\\}",
(String) templateProperties.get(key));
}
text.setValue(textValue);
}
}
System.out.println(texts.size());
}
But its still showing in the docx file.

A Text element in a docx file has parent elements. The text will reside within a Run which in turn will sit within a block element like a paragraph (P node) or a table cell. If you're looking to remove a particular block element based on its textual content, once you've located the relevant text elements, you need to move up the parent elements, and remove them too -- for example, if the ultimate parent is a paragraph node, remove that.
If say, a paragraph displays as 3 lines in Word and you are trying to remove the 2nd line in that paragraph, then you have a different and more challenging problem.

maybe this will help futur people :
if(((org.docx4j.wml.Text) o2).getValue().contains("WhatYouWant")) {
// if your text contains "WhatYouWant" then...
Object o4 =((org.docx4j.wml.Text)o2).getParent();
//gets R
Object o5 = ((org.docx4j.wml.R) o4).getParent();
// gets P
Object o6 = ((org.docx4j.wml.P) o5).getParent();
// gets SdtElement
((List<List<Object>>) o6).remove(o5);
// now you remove your P (paragraph)
}
I had a content control (SdtElement) but I needed to put it in List < List < Object > > don't really know why but.... You might have something else so check in your document.xml before copy/pasting this.
This is for others who have a hard time, like I did to understand docx4j

You could use Apache POI to remove Text from docx file as shown below.
public static void removeTextFromDocx(FileInputStream inpudocxfile, String stringToBeReplaced,
String stringToBeReplacedWith, FileOutputStream outputdocxfile) {
XWPFDocument document = null;
try {
//loading docx file
document = new XWPFDocument(inpudocxfile);
for (XWPFParagraph paragraph : document.getParagraphs()) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//reading an entire paragraph. So size of list is 1 and index of first element is 0
String text = run.getText(0);
if (text != null) {
if (text.contains(stringToBeReplaced)) {
text = text.replace(stringToBeReplaced, stringToBeReplacedWith);
text = text.trim();
run.setText(text, 0);
}
}
}
}
for (XWPFTable table : document.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
for (XWPFParagraph paragraph : cell.getParagraphs()) {
for (XWPFRun run : paragraph.getRuns()) {
String text = run.getText(0);
if (text != null) {
if (text.contains(stringToBeReplaced)) {
text = text.replace(stringToBeReplaced, stringToBeReplacedWith);
text = text.trim();
run.setText(text, 0);
}
}
}
}
}
}
}
document.write(outputdocxfile);
} catch (IOException e) {
LOGGER.error("Could not create outputdocxFile --> IOEXception" + e);
}
}

Related

How to set define different styles for the same paragraph

I'm trying to convert html text to generate a word table. It works pretty well, and the created word file is correct, except the character styles.
This is my first try with Apache POI.
So far, I was able to detect new line (<br>) tags from text paragraph (see code below). But I'd like to also check a few other tags such as <b>, <li>, <font> and set the right run values for each part.
For example :
This is my text <i> which now is in italic<b> but also in bold</b> depending on its importance</i>
I gess I should parse the text, and apply different runs for each part, but I don't know how to do.
private static XWPFParagraph getTableParagraph(XWPFTableCell cell, String text)
{
int fontsize= 11;
XWPFParagraph paragraph = cell.addParagraph();
cell.removeParagraph(0);
paragraph.setSpacingAfterLines(0);
paragraph.setSpacingAfter(0);
XWPFRun myRun1 = paragraph.createRun();
if (text==null) text="";
else
{
while (true)
{
int x = text.indexOf("<br>");
if (x <0) break;
String work = text.substring(0,x );
text= text.substring(x+4);
myRun1.setText(work);
myRun1.addBreak();
}
}
myRun1.setText(text);
myRun1.setFontSize(fontsize);
return paragraph;
}
While converting HTML text one never should go on the HTML using string methods only. XML as well as HTML are markup languages. Their content is markup and not only plain text. The markup needs to be traversed to get all the single nodes together with the meanings out of it. This traversing process never is trivial and so special libraries are there for. Deep inside those libraries also needs using string methods but those are wrapped into useful methods for traversing the markup.
For traversing HTML jsoup may be used for example. Especially NodeTraversor using NodeVisitor is useful for traversing HTML.
My example creates a ParagraphNodeVisitor which implements NodeVisitor. This interface requests method public void head(Node node, int depth) which is called every time the NodeTraversor is on head of a node and public void tail(Node node, int depth) which is called every time the NodeTraversor is on tail of a node. In those methods the process for handling the single nodes can be implemented. In our case main part of the process is whether we need a new XWPFRun and what settings this run needs.
Example:
import java.io.FileOutputStream;
import org.apache.poi.xwpf.usermodel.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.jsoup.select.NodeVisitor;
import org.jsoup.select.NodeTraversor;
public class HTMLtoDOCX {
private XWPFDocument document;
public HTMLtoDOCX(String html, String docxPath) throws Exception {
this.document = new XWPFDocument();
XWPFParagraph paragraph = null;
Document htmlDocument = Jsoup.parse(html);
Elements htmlParagraphs = htmlDocument.select("p");
for(Element htmlParagraph : htmlParagraphs) {
System.out.println(htmlParagraph);
paragraph = document.createParagraph();
createParagraphFromHTML(paragraph, htmlParagraph);
}
FileOutputStream out = new FileOutputStream(docxPath);
document.write(out);
out.close();
document.close();
}
void createParagraphFromHTML(XWPFParagraph paragraph, Element htmlParagraph) {
ParagraphNodeVisitor nodeVisitor = new ParagraphNodeVisitor(paragraph);
NodeTraversor.traverse(nodeVisitor, htmlParagraph);
}
private class ParagraphNodeVisitor implements NodeVisitor {
String nodeName;
boolean needNewRun;
boolean isItalic;
boolean isBold;
boolean isUnderlined;
int fontSize;
String fontColor;
XWPFParagraph paragraph;
XWPFRun run;
ParagraphNodeVisitor(XWPFParagraph paragraph) {
this.paragraph = paragraph;
this.run = paragraph.createRun();
this.nodeName = "";
this.isItalic = false;
this.isBold = false;
this.isUnderlined = false;
this.fontSize = 11;
this.fontColor = "000000";
}
#Override
public void head(Node node, int depth) {
nodeName = node.nodeName();
System.out.println("Start "+nodeName+": " + node);
if ("#text".equals(nodeName)) {
run.setText(((TextNode)node).text​());
} else if ("i".equals(nodeName)) {
isItalic = true;
} else if ("b".equals(nodeName)) {
isBold = true;
} else if ("u".equals(nodeName)) {
isUnderlined = true;
} else if ("br".equals(nodeName)) {
run.addBreak();
} else if ("font".equals(nodeName)) {
fontColor = (!"".equals(node.attr("color")))?node.attr("color").substring(1):"000000";
fontSize = (!"".equals(node.attr("size")))?Integer.parseInt(node.attr("size")):11;
}
run.setItalic(isItalic);
run.setBold(isBold);
if (isUnderlined) run.setUnderline(UnderlinePatterns.SINGLE); else run.setUnderline(UnderlinePatterns.NONE);
run.setColor(fontColor); run.setFontSize(fontSize);
}
#Override
public void tail(Node node, int depth) {
nodeName = node.nodeName();
System.out.println("End "+nodeName);
if ("#text".equals(nodeName)) {
run = paragraph.createRun(); //after setting the text in the run a new run is needed
} else if ("i".equals(nodeName)) {
isItalic = false;
} else if ("b".equals(nodeName)) {
isBold = false;
} else if ("u".equals(nodeName)) {
isUnderlined = false;
} else if ("br".equals(nodeName)) {
run = paragraph.createRun(); //after setting a break a new run is needed
} else if ("font".equals(nodeName)) {
fontColor = "000000";
fontSize = 11;
}
run.setItalic(isItalic);
run.setBold(isBold);
if (isUnderlined) run.setUnderline(UnderlinePatterns.SINGLE); else run.setUnderline(UnderlinePatterns.NONE);
run.setColor(fontColor); run.setFontSize(fontSize);
}
}
public static void main(String[] args) throws Exception {
String html =
"<p>Text without tags. <b> Then bold <br/> having break.</b> Then without tags again.</p>"
+"<p><font size='32' color='#0000FF'><b>First paragraph.</font></b><br/>Just like a heading</p>"
+"<p>This is my text <i>which now is in italic <b>but also in bold</b> depending on its <u>importance</u></i>.<br/>Now a <b><i><u>new</u></i></b> line starts <i>within <b>the same</b> paragraph</i>.</p>"
+"<p><b>Last <u>paragraph <i>comes</u> here</b> finally</i>.</p>"
+"<p>But yet <u><i><b>another</i></u></b> paragraph having <i><font size='22' color='#FF0000'>special <u>font</u> settings</font></i>. Now default font again.</p>"
;
HTMLtoDOCX htmlToDOCX = new HTMLtoDOCX(html, "./CreateWordParagraphFromHTML.docx");
}
}
Result:
Disclaimer: This is a working draft showing the principle. Neither it is fully ready nor it is code ready for use in productive environments.

abas-ERP: Copy TN field into another TN field with edp/epi

I want to create an invoice and fill the field pftext (TN2) with the content of another TN2 field called ytdescription.
My problem is, that I always have the path of my textfield in pftext but not the content of my textfield.
1st try of edp header:
kunde;artex;pftext;mge
'M|ytkdnr';'M|ytartnr';'M|ytdescription';'M|ytmge'
2nd try:
kunde;artex;*pftext;mge;
'M|ytkdnr';'M|ytartnr';'M|ytdescription';'M|ytmge'
Of course I could create a T254 field and store the content of M|ytdescription in the new field, but then I am stuck to max 3000 chars for the content.
Many other tries followed but with no success :-(
Any help is highly appreciated!
I Don't know the options to do this per EDP, but there is a solution to do this per AJO:
public int run(DbContext dbContext, String[] args) {
/*
* Get the object to copy data from, in this case a customer with idno 70001
*/
String idno = "70001";
SelectionBuilder<Customer> selectionBuilder = SelectionBuilder.create(Customer.class);
selectionBuilder.add(Conditions.eq(Customer.META.idno, idno));
Customer customer = QueryUtil.getFirst(dbContext, selectionBuilder.build());
VendorEditor vendorEditor = null;
// Read the original content to a StringWriter
StringWriter originalFreeText = new StringWriter();
try {
customer.getFreeText(originalFreeText);
// Create a new object to write the values to. In example a Supplier
vendorEditor = dbContext.newObject(VendorEditor.class);
vendorEditor.setSwd("Searchword");
vendorEditor.setAddr("Vendor Name");
if (!originalFreeText.toString().equals("")) {
vendorEditor.setFreeText(new StringReader(originalFreeText.toString()));
}
vendorEditor.commit();
} catch (IOException e) {
dbContext.out().println(e.getMessage());
} finally {
if (vendorEditor != null) {
if (vendorEditor.active()) {
vendorEditor.abort();
}
}
}
return 0;
}

what is "Dojo Name Text Box" onremove event?

I have a Dojo Name Text Box "xe:djextNameTextBox" on my form. By clicking [x] it removes a name from the list. How do I check what exact name was removed or clicked without parsing all values in getComponent("myNameBox").getValue()?
In my opinion the only way to handle this issue is to parse all values to get the information which name exactly was removed. I had to deal with the same problem in a previous project.
XPage
Set viewScope var "viewFiltersAsString":
<xp:this.beforeRenderResponse><![CDATA[#{javascript:log('beforerenderresponse (start rendering)');
requestScope.start = new Date().getTime();
viewScope.put('viewFiltersAsString', viewController.getViewFiltersAsString());
viewController.actionSetPage();}]]></xp:this.beforeRenderResponse>
Extension Library Dojo Name List Text Box control:
<xe:djextListTextBox id="djFilters" multipleSeparator=","
value="#{viewScope.viewFiltersAsString}" displayLabel="true"
title="Hier klicken um Filter zu löschen">
<xe:this.dataProvider>
<xe:simpleValuePicker caseInsensitive="false"
labelSeparator="~" valueListSeparator=","
valueList="#{viewController.viewFiltersLabelsAsString}">
</xe:simpleValuePicker>
</xe:this.dataProvider>
<xp:eventHandler event="onChange" submit="true" refreshMode="partial"
refreshId="${javascript:compositeData.refreshId}" execMode="partial"
action="#{javascript:viewController.setViewFiltersAsString(#Trim(viewScope.get('viewFiltersAsString')).toString())}">
</xp:eventHandler>
</xe:djextListTextBox>
Managed Bean "viewController"
/**
* Converts all filters to a useable format for dojo List Textbox
* #return string of filters
*/
public String getViewFiltersAsString() {
String filtersAsString = "";
for (ViewFilter filter : viewFilters) {
if (filtersAsString == "") filtersAsString = filter.getKey();
else filtersAsString += "," + filter.getKey();
}
return filtersAsString;
}
public void setViewFiltersAsString(String viewFiltersAsString) {
if (viewFiltersAsString != null && !viewFiltersAsString.equals("")) {
List<String> currentfilters = Converter.toList(",", viewFiltersAsString);
for (ViewFilter filter : viewFilters) {
boolean remove = true;
for (String currentFilter : currentfilters) {
if (filter.getKey().equals(currentFilter)) {
remove = false;
break;
}
}
if (remove) {
// user can click only one filter at one time
setActionViewFilter(viewFilters.removeFilter(filter));
break;
}
}
} else {
setActionViewFilter(viewFilters.removeFilter(viewFilters.get(0)));
}
}
Hint: viewFilters is a java.util.List of ViewFilter objects and a ViewFilter is a simple java class holding information about a filter (in your case a name)

Text extraction from table cells

I have a pdf. The pdf contains a table. The table contains many cells (>100). I know the exact position (x,y) and dimension (w,h) of every cell of the table.
I need to extract text from cells using itextsharp. Using PdfReaderContentParser + FilteredTextRenderListener (using a code like this http://itextpdf.com/examples/iia.php?id=279 ) I can extract text but I need to run the whole procedure for each cell. My pdf have many cells and the program needs too much time to run. Is there a way to extract text from a list of "rectangle"? I need to know the text of each rectangle. I'm looking for something like PDFTextStripperByArea by PdfBox (you can define as many regions as you need and the get text using .getTextForRegion("region-name") ).
This option is not immediately included in the iTextSharp distribution but it is easy to realize. In the following I use the iText (Java) class, interface, and method names because I am more at home with Java. They should easily be translatable into iTextSharp (C#) names.
If you use the LocationTextExtractionStrategy, you can can use its a posteriori TextChunkFilter mechanism instead of the a priori FilteredRenderListener mechanism used in the sample you linked to. This mechanism has been introduced in version 5.3.3.
For this you first parse the whole page content using the LocationTextExtractionStrategy without any FilteredRenderListener filtering applied. This makes the strategy object collect TextChunk objects for all PDF text objects on the page containing the associated base line segment.
Then you call the strategy's getResultantText overload with a TextChunkFilter argument (instead of the regular no-argument overload):
public String getResultantText(TextChunkFilter chunkFilter)
You call it with a different TextChunkFilter instance for each table cell. You have to implement this filter interface which is not too difficult as it only defines one method:
public static interface TextChunkFilter
{
/**
* #param textChunk the chunk to check
* #return true if the chunk should be allowed
*/
public boolean accept(TextChunk textChunk);
}
So the accept method of the filter for a given cell must test whether the text chunk in question is inside your cell.
(Instead of separate instances for each cell you can of course also create one instance whose parameters, i.e. cell coordinates, can be changed between getResultantText calls.)
PS: As mentioned by the OP, this TextChunkFilter has not yet been ported to iTextSharp. It should not be hard to do so, though, only one small interface and one method to add to the strategy.
PPS: In a comment sschuberth asked
Do you then still call PdfTextExtractor.getTextFromPage() when using getResultantText(), or does it somehow replace that call? If so, how to you then specify the page to extract to?
Actually PdfTextExtractor.getTextFromPage() internally already uses the no-argument getResultantText() overload:
public static String getTextFromPage(PdfReader reader, int pageNumber, TextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
return parser.processContent(pageNumber, strategy, additionalContentOperators).getResultantText();
}
To make use of a TextChunkFilter you could simply build a similar convenience method, e.g.
public static String getTextFromPage(PdfReader reader, int pageNumber, LocationTextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators, TextChunkFilter chunkFilter) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
return parser.processContent(pageNumber, strategy, additionalContentOperators).getResultantText(chunkFilter);
}
In the context at hand, though, in which we want to parse the page content only once and apply multiple filters, one for each cell, we might generalize this to:
public static List<String> getTextFromPage(PdfReader reader, int pageNumber, LocationTextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators, Iterable<TextChunkFilter> chunkFilters) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
parser.processContent(pageNumber, strategy, additionalContentOperators)
List<String> result = new ArrayList<>();
for (TextChunkFilter chunkFilter : chunkFilters)
{
result.add(strategy).getResultantText(chunkFilter);
}
return result;
}
(You can make this look fancier by using Java 8 collection streaming instead of the old'fashioned for loop.)
Here's my take on how to extract text from a table-like structure in a PDF using itextsharp. It returns a collection of rows and each row contains a collection of interpreted columns. This may work for you on the premise that there is a gap between one column and the next which is greater than the average width of a single character. I also added an option to check for wrapped text within a virtual column. Your mileage may vary.
using (PdfReader pdfReader = new PdfReader(stream))
{
for (int page = 1; page <= pdfReader.NumberOfPages; page++)
{
TableExtractionStrategy tableExtractionStrategy = new TableExtractionStrategy();
string pageText = PdfTextExtractor.GetTextFromPage(pdfReader, page, tableExtractionStrategy);
var table = tableExtractionStrategy.GetTable();
}
}
public class TableExtractionStrategy : LocationTextExtractionStrategy
{
public float NextCharacterThreshold { get; set; } = 1;
public int NextLineLookAheadDepth { get; set; } = 500;
public bool AccomodateWordWrapping { get; set; } = true;
private List<TableTextChunk> Chunks { get; set; } = new List<TableTextChunk>();
public override void RenderText(TextRenderInfo renderInfo)
{
base.RenderText(renderInfo);
string text = renderInfo.GetText();
Vector bottomLeft = renderInfo.GetDescentLine().GetStartPoint();
Vector topRight = renderInfo.GetAscentLine().GetEndPoint();
Rectangle rectangle = new Rectangle(bottomLeft[Vector.I1], bottomLeft[Vector.I2], topRight[Vector.I1], topRight[Vector.I2]);
Chunks.Add(new TableTextChunk(rectangle, text));
}
public List<List<string>> GetTable()
{
List<List<string>> lines = new List<List<string>>();
List<string> currentLine = new List<string>();
float? previousBottom = null;
float? previousRight = null;
StringBuilder currentString = new StringBuilder();
// iterate through all chunks and evaluate
for (int i = 0; i < Chunks.Count; i++)
{
TableTextChunk chunk = Chunks[i];
// determine if we are processing the same row based on defined space between subsequent chunks
if (previousBottom.HasValue && previousBottom == chunk.Rectangle.Bottom)
{
if (chunk.Rectangle.Left - previousRight > 1)
{
currentLine.Add(currentString.ToString());
currentString.Clear();
}
currentString.Append(chunk.Text);
previousRight = chunk.Rectangle.Right;
}
else
{
// if we are processing a new line let's check to see if this could be word wrapping behavior
bool isNewLine = true;
if (AccomodateWordWrapping)
{
int readAheadDepth = Math.Min(i + NextLineLookAheadDepth, Chunks.Count);
if (previousBottom.HasValue)
for (int j = i; j < readAheadDepth; j++)
{
if (previousBottom == Chunks[j].Rectangle.Bottom)
{
isNewLine = false;
break;
}
}
}
// if the text was not word wrapped let's treat this as a new table row
if (isNewLine)
{
if (currentString.Length > 0)
currentLine.Add(currentString.ToString());
currentString.Clear();
previousBottom = chunk.Rectangle.Bottom;
previousRight = chunk.Rectangle.Right;
currentString.Append(chunk.Text);
if (currentLine.Count > 0)
lines.Add(currentLine);
currentLine = new List<string>();
}
else
{
if (chunk.Rectangle.Left - previousRight > 1)
{
currentLine.Add(currentString.ToString());
currentString.Clear();
}
currentString.Append(chunk.Text);
previousRight = chunk.Rectangle.Right;
}
}
}
return lines;
}
private struct TableTextChunk
{
public Rectangle Rectangle;
public string Text;
public TableTextChunk(Rectangle rect, string text)
{
Rectangle = rect;
Text = text;
}
public override string ToString()
{
return Text + " (" + Rectangle.Left + ", " + Rectangle.Bottom + ")";
}
}
}

Telerik Mail Merge - Friendly Names (using RadRichTextBox)

I think i'm missing something obvious...
I'm using the Telerik Rad controls for WPF but i assume that the Rich text box uses some similar implementation for the mail merge functionality.
I want to have some friendly names on my mail merge fields. (namely spaces in the field names)
So i have a class for instance
Public Class someclass
{
<DisplayName("This is the complex description of the field")>
Public property thisfieldnamehasacomplexdescription as string
Public property anothercomplexfield as string
}
This is the only way i know to get "Friendly" names in the dropdown that is the mail merge.
So the two fields turn up okay as :
"This is the complex description of the field"
"anothercomplexfield"
but only anothercomplexfield actually populates with data when you do the merge.
Am i going to have to template the raddropdownbutton that holds the mail merge fields?
Is there an example of this somewhere?
Also a sub question. How do i add a scroll bar on these things?
(also i know this board is not a TELERIK specific board (duh!) but this might be useful to someone in the future. So i'll copy the answer i get from Telerik into here!
http://www.telerik.com/community/forums/wpf/richtextbox/558428-radrichtextbox-mailmerge---using-displayname-to-create-a-friendly-name-with-spaces.aspx )
This is what telerik gave me:
With the default MergeFields, it is not possible to change the display name fragment of the field in order to achieve a more friendly look. This should be possible if you implement a custom MergeField by deriving from the MergeField class. Here is a sample implementation that shows how this can be done:
public class CustomMergeField : MergeField
{
private const string CustomFieldName = "CustomField";
static CustomMergeField()
{
CodeBasedFieldFactory.RegisterFieldType(CustomMergeField.CustomFieldName, () => { return new CustomMergeField(); });
}
public override string FieldTypeName
{
get
{
return CustomMergeField.CustomFieldName;
}
}
public override Field CreateInstance()
{
return new CustomMergeField();
}
protected override DocumentFragment GetDisplayNameFragment()
{
return base.CreateFragmentFromText(string.Format(Field.DisplayNameFragmentFormat, this.GetFriendlyFieldName(this.PropertyPath)));
}
private string GetFriendlyFieldName(string fieldName)
{
int lettersInEnglishAlphabet = 26;
List<char> separators = new List<char>(lettersInEnglishAlphabet);
for (int i = 0; i < lettersInEnglishAlphabet; i++)
{
separators.Add((char)('A' + i));
}
StringBuilder newFieldName = new StringBuilder();
int previousIndex = 0;
for (int i = 1; i < fieldName.Length; i++)
{
if (separators.Contains(fieldName[i]))
{
if (previousIndex > 0)
{
newFieldName.Append(" ");
}
newFieldName.Append(fieldName.Substring(previousIndex, i - previousIndex));
previousIndex = i;
}
}
newFieldName.Append(" " + fieldName.Substring(previousIndex));
return newFieldName.ToString();
}
}
Note that the fragment that is shown when the DisplayMode is Code cannot be changed.
As for your other question, you can change the content of the dropdown button to show the friendly name of the fields and to include a scrollbar in the following way:
1. First, remove the binding of the button to the InsertMergeFieldEmptyCommand from XAML and give it a name (e.g. insertMergeField).
2. Next, add the following code in code-behind:
AddMergeFieldsInDropDownContent(this.insertMergeFieldButton);
private void AddMergeFieldsInDropDownContent(RadRibbonDropDownButton radRibbonDropDownButton)
{
Grid grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(100, GridUnitType.Pixel) });
ScrollViewer scrollViewer = new ScrollViewer();
scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
StackPanel stackPanel = new StackPanel();
foreach (string fieldName in this.editor.Document.MailMergeDataSource.GetColumnNames())
{
RadRibbonButton fieldButton = new RadRibbonButton()
{
Text = this.GetFriendlyFieldName(fieldName),
Size = ButtonSize.Medium,
HorizontalAlignment = HorizontalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left
};
fieldButton.Command = this.editor.Commands.InsertFieldCommand;
fieldButton.CommandParameter = new MergeField() { PropertyPath = fieldName };
//or
//fieldButton.CommandParameter = new CustomMergeField() { PropertyPath = fieldName };
stackPanel.Children.Add(fieldButton);
}
stackPanel.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
scrollViewer.Content = stackPanel;
grid.Children.Add(scrollViewer);
radRibbonDropDownButton.DropDownContent = grid;
}
You can, of course optimize the code of the GetFriendlyName method and add it in a way that will be available by both classes.