[EN] C1TextParser를 사용하여 스마트 태그 시스템을 구현하는 방법
페이지 정보
작성자 GrapeCity 작성일 2021-11-04 16:53 조회 1,787회 댓글 0건본문
관련링크
In this post, we are going to see how we can implement a SmartTag system using one of our Service library: . is a feature initially introduced in Microsoft Word which recognizes parts of text, highlights them in some form, and optionally adds an action which can be performed using that part of the text.
Some of the things which can be augmented using a Smart Tag include a phone number, tracking number, an email, a contact name, etc. By having an application support SmartTags, it allows the end-user to have a feature-rich document that provides the ability to interact with their documents in a way which would be impossible to do with a plain text document.
You can see how useful this can be from the following demo:
By adding a few SmartTags, we're able to:
See useful information from the document at a glance (the stock ticker, which stocks closed up/down)
View a summary of a particular stock from the Tooltip
View price trend for a particular stock by clicking on its symbol
Add the symbol for a new Stock and immediately do all the things we could do with existing parts of the document
Compare the Price trend for 2 (or more) Stocks
Edit a date text anywhere in the document using DatePicker control
So, we have seen what SmartTags can do for us. Let's see how we can implement a SmartTag system using C1TextParser library.
Before we start, we need to decide how and where we are going to have the text information. Since C1TextParser only performs operations on a text stream, we’ll need a document and the ability to manipulate parts of the document visually. is perfect for this since it provides APIs to edit and manipulate parts of the document.
Now that we have decided what we are going to use, here’s what we’ll do:
Create a simple WPF application with C1RichTextBox
Create a Parser using C1TextParser to create Smart Tags
Format the created Smart Tags using C1RichTextBox
Associate an action with the Smart Tag
Create a WPF Application with C1RichTextBox
We first create a WPF application and add C1RichTextBox in MainWindow.xaml:
We also add a simple HTML document which will be loaded in C1RichTextBox:
MainWindow.xaml.cs
c1RichTextBox.Html = System.IO.File.ReadAllText("Data\\document.html");
document.html
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title></title> </head> <body style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 1.5em;"> <h2>Stock Portfolio</h2> <ul> <li>Apple (AAPL) +2.34%</li> <li>Bank of America (BAC) -3.25%</li> <li>Coca-Cola (KO) +0.47%</li> <li>Wells Fargo (WFC) +0.20%</li> <li>American Express (AXP) -0.8%</li> </ul> </body> </html>
Next, we will make this simple document more interactive by using Smart Tags. For that we'll need a Parser to create these Smart Tags.
Create a Parser Using C1TextParser to Create Smart Tags
Configure C1TextParser
First, we’ll need to install the Nuget package for C1TextParser. We can do so by using the Nuget Package Manager:
Since we have created a WPF application, we install C1.WPF.TextParser.
Create a Smart Tag Template Document
We want our Smart Tag system to be easily modifiable so we are going to create a template for this purpose. C1TextParser’s is ideal for this purpose, however, we don’t just want to create one Smart Tag, we also want it to be extensible.
We should create a custom template structure which can have the actual parsing template embedded in it.
smart-tag-template.xml
<SmartTags> <SmartTag name="StockTicker"> <ParserTemplate> <template name="StockTicker" extractFormat="regex:[A-Z]{1,4}" /> </ParserTemplate> <Styles> <Style name="BorderBrush" value="#FF008080" /> <Style name="BorderThickness" value="0,0,0,3" /> <Style name="Background" value="#FFEEE8AA" /> </Styles> </SmartTag> </SmartTags>
Here, we’ve added two elements for a Smart Tag:
ParserTemplate – Parsing template used by C1TextParser
Styles – Styling properties used by C1RichTextBox to highlight the Smart Tag
Create a Utility to Read a Smart Tag Template
Since we created a custom XML document to store configuration for the Smart Tags, we also need something to read this document.
For this purpose, we create a class named TemplateReader:
TemplateReader.cs
public class TemplateReader { private XmlDocument document; public TemplateReader(string filepath) { document = new XmlDocument(); document.Load(filepath); } }
We also add a few methods in TemplateReader to get different elements from our Smart Tag template:
TemplateReader.cs
// Gets the names of all Smart Tags public IEnumerable<string> GetSmartTags() { var nodes = document.SelectSingleNode("/SmartTags").ChildNodes; foreach (XmlNode node in nodes) { yield return node.Attributes["name"].Value; } } // Gets the Parsing template for the given Smart Tag public string GetSmartTagTemplate(string name) { XmlNode node = document.SelectSingleNode($"/SmartTags/SmartTag[@name='{name}']"); XmlNode templateNode = node.SelectSingleNode("ParserTemplate"); return templateNode.InnerXml; }
The Styles element is a little different. We will eventually use of C1RichTextBox to highlight the SmartTags.
We need to create an object of from the Style nodes at runtime. We can do this using reflection:
TemplateReader.cs
public C1TextElementStyle GetSmartTagStyle(string name) { XmlNode node = document.SelectSingleNode($"/SmartTags/SmartTag[@name='{name}']"); XmlNode styles = node.SelectSingleNode("Styles"); C1TextElementStyle style = new C1TextElementStyle(); foreach (XmlNode styleNode in styles.ChildNodes) { if (styleNode.NodeType != XmlNodeType.Element) continue; string styleName = styleNode.Attributes["name"].Value; var propertyInfo = typeof(C1TextElement).GetProperties(BindingFlags.Instance | BindingFlags.Public). Where(prop => prop.Name == styleName).FirstOrDefault(); StyleProperty styleProperty = (StyleProperty)typeof(C1TextElement).GetFields(). Where(field => field.Name == styleName + "Property").FirstOrDefault().GetValue(null); string styleValue = styleNode.Attributes["value"].Value; Type propType = propertyInfo.PropertyType; if (propType == typeof(Brush)) { style[styleProperty] = new SolidColorBrush((Color)ColorConverter.ConvertFromString(styleValue)); } return style; }
Above snippet shows how we can read the value for a Color. Other types of Style properties can be read similarly.
Create Parsers for SmartTags
We have seen how we can store Smart Tag configuration in an XML document and then read it. Now, let’s put it together to create Parsers for the Smart Tags.
Since using Parsers for each type of Smart Tag is quite cumbersome, we abstract this into a single Parser and let it handle all the details about the XML template and its reader.
SmartTagParsers.cs
public class SmartTagParsers { private Dictionary<string, IExtractor> extractors; private TemplateReader templateReader; public SmartTagParsers() { extractors = new Dictionary<string, IExtractor>(); } public void Add(string name, IExtractor extractor) { extractors.Add(name, extractor); } }
Here, we created a class named SmartTagParsers which will have IExtractor objects for each type of Smart Tag. is the interface used by C1TextParser to parse text, so we just map an IExtractor with a Smart Tag name using the Dictionary object extractors.
We also add a Parse method to our SmartTagParsers which will create a List of SmartTag objects that are found in given text. Since TemplateBasedExtractors parse the result into a JSON object we convert it to a simple SmartTag object.
SmartTag.cs
public class SmartTag { public string Name { get; } public string Text { get; } public SmartTag(string name, string text) { Name = name; Text = text; } }
SmartTagParsers.cs
public List<SmartTag> Parse(string text) { List<SmartTag> smartTags = new List<SmartTag>(); foreach (var entry in extractors) { using (var stream = text.ToStream()) { var result = entry.Value.Extract(stream); string json = result.ToJsonString(); JObject jObject = JsonConvert.DeserializeObject<JObject>(json); jObject = jObject.Value<JObject>("Result"); JToken token = jObject.Value<JToken>(entry.Key); if (token?.Type == JTokenType.Array) { foreach (var childToken in token.Children()) { smartTags.Add(new SmartTag(entry.Key, childToken.ToString())); } } } } return smartTags; }
Finally, we add a GetStyle method which just obtains the Style from the TemplateReader:
SmartTagParsers.cs
public C1TextElementStyle GetStyle(string name) { return templateReader.GetSmartTagStyle(name); }
Format the Created Smart Tags Using C1RichTextBox
We are now ready to use the Parser with C1RichTextBox. For this, we first create an object of C1RangeStyleCollection and add this in StyleOverrides of C1RichTextBox.
MainWindow.xaml.cs
public partial class MainWindow : Window { private C1RangeStyleCollection rangeStyles = new C1RangeStyleCollection(); private SmartTagParsers smartTagParsers; private List<SmartTag> smartTags; public MainWindow() { InitializeComponent(); c1RichTextBox.StyleOverrides.Add(rangeStyles); smartTagParsers = new SmartTagParsers(); smartTagParsers.LoadTemplate("Parser\\smart-tag-template.xml"); c1RichTextBox.Html = System.IO.File.ReadAllText("Data\\document.html"); } }
We then add some methods to parse the document and fill this Style collection for each Smart Tag:
MainWindow.xaml.cs
private void AnnotateSmartTags(C1RichTextBox richTextBox) { smartTags = smartTagParsers.Parse(richTextBox.Text); AddSmartTags(richTextBox, smartTags); } private void AddSmartTags(C1RichTextBox richTextBox, List<SmartTag> smartTags) { rangeStyles.Clear(); foreach (SmartTag smartTag in smartTags) { foreach (Match match in Regex.Matches(c1RichTextBox.Text, Regex.Escape(smartTag.Text))) { var textRange = richTextBox.GetTextRange(match.Index, match.Length); C1TextElementStyle style = smartTagParsers.GetStyle(smartTag.Name); rangeStyles.Add(new C1RangeStyle(textRange, style)); } } }
With these changes, the document looks like this:
Now we can highlight some useful information in the document; however, it would be more helpful if we could do something with these tags. For this, we can associate an Action with a SmartTag.
Associating an Action with a SmartTag
To associate an Action with a Smart Tag we first identify which part of the document is clicked by the user, and if it is a Smart Tag then show some useful information.
We do this using a utility method that identifies the text portion (for a particular Run of the document) currently residing under a given point:
MainWindow.xaml.cs
private C1TextRange GetElementUnderPoint(C1Run c1Run, Point point) { var pointer = c1RichTextBox.GetPositionFromPoint(point); string text = c1Run.Text; int start = pointer.Offset; int end = pointer.Offset; while (start > 0 && char.IsLetterOrDigit(text, start - 1)) { start -= 1; } while (end < text.Length - 1 && char.IsLetterOrDigit(text, end + 1)) { end += 1; } var word = new C1TextRange(pointer.Element, start, end - start + 1); return word; }
After this, we add an event handler for C1RichTextBox’s ElementMouseLeftButtonUp event:
MainWindow.xaml.cs
private void C1RichTextBox_ElementMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { bool isCtrlKeyDown = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); if (isCtrlKeyDown && sender is C1Run c1Run) { Point point = e.GetPosition(c1RichTextBox); var word = GetElementUnderPoint(c1Run, point); if (smartTags.Any(tag => tag.Text == word.Text)) { word.Runs.First().Cursor = Cursors.Hand; StockInfo info = stockService.GetStockInfo(word.Text); if (info != null && info.Quotes.Count > 0) { var stockTrend = new StockTrend(info); stockTrend.Owner = this; stockTrend.Show(); } } } }
In the handler, we check if the Control key is pressed. If it is, we try to get stock data for the text portion that was clicked. We use a StockService class to get this data from a JSON file, which can be easily modified to use a real-time Stock API.
Once we have the necessary data, we show this in a window where the data is plotted using C1FinancialChart.
Here’s the XAML snippet for the StockTrend window we have used:
StockTrend.xaml
<Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> </Grid.RowDefinitions> <c1:C1FinancialChart x:Name="financialChart" ChartType="Candlestick" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding StockInfo.Quotes}" Grid.Row="0"> <c1:FinancialSeries Binding="High,Low,Open,Close,Volume" BindingX="Date" SeriesName="{x:Null}" ChartType="Line"> </c1:FinancialSeries> <c1:C1FinancialChart.Layers> <c1:C1LineMarker x:Name="marker" Lines="Both" Interaction="Move" Alignment="Auto" DragLines="True" PositionChanged="positionChanged"/> </c1:C1FinancialChart.Layers> </c1:C1FinancialChart> </Grid>
We can interact with the created SmartTags and make it even more useful:
Creating more SmartTags is now easy. We just need to do two things:
Add the regex and style information in the SmartTag template,
If required, add an Action for the SmartTag by using necessary events and properties of the editor.
To check the implementation for the demo shown in the beginning of this post, download the .
지금 바로 ComponentOne을 다운로드하여 직접 테스트해보세요!
댓글목록
등록된 댓글이 없습니다.