I'm dealing with the dreaded <Run/>
in Silverlight 3 and having to programmatically create a <TextBlock>
and its inlines:
Why dreaded? Because it doesn't work, I guess, the way you'd expect. Exhibit A, below, should produce
BARN(with fancy colors for each character), but instead it produces:
B A R N
EXHIBIT A
<TextBlock FontFamily="Comic Sans MS" FontSize="88">
<Run Foreground="#A200FF">B</Run>
<Run Foreground="#FF0000">A</Run>
<Run Foreground="#FFC000">R</Run>
<Run Foreground="#FFFF00">N</Run>
</TextBlock>
What does produce the desired result, however, is:
EXHIBIT B
<TextBlock FontFamily="Comic Sans MS" FontSize="88">
<Run Foreground="#A200FF">B</Run><Run Foreground="#FF0000">A</Run><Run Foreground="#FFC000">R</Run><Run Foreground="#FFFF00">N</Run>
</TextBlock>
Stupid, eh? Anyway, this is documented @ XAML Processing Differences Between Silverlight 3 and Silverlight 4 under Whitespace Handling where it says:
Silverlight 3 treats whitespace more literally in a wider range, including some cases where CLRF is considered significant. This sometimes led to file-format XAML with omitted CRLF in order to avoid unwanted whitespace in the presentation, but which was not human-readable in editing environments. Silverlight 4 uses a more intuitive significant-whitespace model that is similar to WPF. This model collapses file-formatting whitespace in most cases, with exception of certain CLR-attributed containers that treat all whitespace as significant. This whitespace model gives editing environments greater freedom to introduce whitespace that can improve XAML code formatting. Also, Silverlight 4 has text elements that permit even greater control over whitespace presentation issues.
Great, but I'm not using SL4 because I'm creating a WP7 app programmatically. Yeah, my XAML is generated. Using XML Literals. Then sent to a string. Like this:
Dim r1 As XElement = <Run Foreground="#A200FF">B</Run>
Dim r2 As XElement = <Run Foreground="#FF0000">A</Run>
Dim r3 As XElement = <Run Foreground="#FFC000">R</Run>
Dim r4 As XElement = <Run Foreground="#FFFF00">N</Run>
Dim tb = <TextBlock FontFamily="Comic Sans MS" FontSize="88">
<%= r1 %><%= r2 %><%= r3 %><%= r4 %>
</TextBlock>
Dim result = tb.ToString
After all this, here's my question: How can I produce Exhibit B instead of Exhibit A. That textblock will become part of a greater number of elements in a XAML page, so the .ToString
part isn't exactly accurate in this location - that happens when all of the XAML for the user control page is kicked out to file.
EDIT (6 May 2011): A little progress and a bounty
I've made a bit of progress as below, but I'm running up against a mental block here on how to accomplish an unusual split and processing of XML to output a string. Take this new example:
<Canvas>
<Grid>
<TextBlock>
<Run Text="r"/>
<Run Text="u"/>
<Run Text="n"/>
</TextBlock>
<TextBlock>
<Run Text="far a"/>
<Run Text="way"/>
<Run Text=" from me"/>
</TextBlock>
</Grid>
<Grid>
<TextBlock>
<Run Text="I"/>
<Run Text=" "/>
<Run Text="want"/>
<LineBreak/>
</TextBlock>
<TextBlock>
<LineBreak/>
<Run Text="...thi"/>
<Run Text="s to"/>
<LineBreak/>
<Run Text=" work"/>
</TextBlock>
</Grid>
</Canvas>
I want the output string to be:
<Canvas>
<Grid>
<TextBlock>
<Run Text="r"/><Run Text="u"/><Run Text="n"/>
</TextBlock>
<TextBlock>
<Run Text="far a"/><Run Text="way"/><Run Text=" from me"/>
</TextBlock>
</Grid>
<Grid>
<TextBlock>
<Run Text="I"/><Run Text=" "/><Run Text="want"/>
<LineBreak/>
</TextBlock>
<TextBlock>
<LineBreak/>
<Run Text="...thi"/><Run Text="s to"/>
<LineBreak/>
<Run Text=" work"/>
</TextBlock>
</Grid>
</Canvas>
I've been looking at the XMLWriter
and XMLWriterSettings
, based on Eric White's post, which seems to be a good start for the runs (not including the potential <LineBreak/>
s yet, which also stumps me). Like this:
Sub Main()
Dim myXML As XElement = <Canvas>
<Grid>
<TextBlock>
<Run Text="r"/>
<Run Text="u"/>
<Run Text="n"/>
</TextBlock>
<TextBlock>
<Run Text="far a"/>
<Run Text="way"/>
<Run Text=" from me"/>
</TextBlock>
</Grid>
</Canvas>
Console.Write(ToXMLString(myXML))
Console.ReadLine()
End Sub
Public Function ToXMLString(xml As XElement) As String
Dim tb As XElement = xml.Elements.<TextBlock>.FirstOrDefault
Dim xmlWriterSettings As New XmlWriterSettings
XmlWriterSettings.NewLineHandling = NewLineHandling.None
XmlWriterSettings.OmitXmlDeclaration = True
Dim sb As New StringBuilder
Using xmlwriter As XmlWriter = xmlwriter.Create(sb, XmlWriterSettings)
tb.WriteTo(xmlwriter)
End Using
Return sb.ToString
End Function
But I'm having a huge problem going much further with figuring out how to parse this to produce the desired output above.
I'm not sure I'm grasping your VB.NET xml syntax stuff. However ultimately you are auto generating Xaml which is fundementally a string which is shoved through the XamlParser. Since you are generating the Xaml with code you aren't going to need to edit the results manually at any point.
Hence why not just strip out all the CR and LF characters from the final string before sending it to the XamlParser?
Here's another approach you can try. Works incredibly well with the tests I've made.
This takes advantage of LINQ to XML and Regular Expressions. The idea is to comment out all
Run
elements using a specially crafted comment and get the string representation. Then scan looking for consecutiveRun
elements and "merge" them together. Then when that is done, "uncomment" all theRun
elements back.And the output:
I don't know if i got your question right but here is an example that I think your looking for:
C#
VB.NET (i used the developerfusion converter so bear with me):
You can use the overload of
ToString()
that allows you to specify theSaveOptions
so you can disable formatting. However I don't think you can set save options for individualXElements
however. You'll have to write out the string manually like you already have. Here's a partial implementation, though it just writes out TextBlocks all on one line. Would that be an acceptable formatting?And the output:
The key to solving this problem is to write a recursive function that iterates through the XML tree, writing the various elements and attributes to specially created XmlWriter objects. There is an 'outer' XmlWriter object that writes indented XML, and an 'inner' XmlWriter object that writes non-indented XML.
The recursive function initially uses the 'outer' XmlWriter, writing indented XML, until it sees the TextBlock element. When it encounters the TextBlock element, it creates the 'inner' XmlWriter object, writing the child elements of the TextBlock element to it. It also writes white space to the 'inner' XmlWriter.
When the 'inner' XmlWriter object is finished with writing the TextBlock element, the text that the writer wrote is written to the 'outer' XmlWriter using the WriteRaw method.
The advantages of this approach is that there is no post-processing of the XML. It is extremely difficult to post-process XML and be certain that you have properly handled all cases, including arbitrary text in CData nodes, etc. All of the XML is written using only the XmlWriter class, thereby ensuring that this will always write valid XML. The only exception to this is the specially crafted white-space that is written using the WriteRaw method, which achieves the desired indenting behavior.
One key point is that the 'inner' XmlWriter object's conformance level is set to ConformanceLevel.Fragment, because the 'inner' XmlWriter needs to write XML that does not have a root element.
To achieve the desired formatting of Run elements (i.e. Run elements that are adjacent have no insignificant white space between them), the code uses the GroupAdjacent extension method. Some time ago, I write a blog post on the GroupAdjacent extension method for VB.
When you run the code using the specified sample XML, it outputs:
Following is the complete listing of the VB.NET example program. In addition, I've written a blog post, Custom Formatting of XML using LINQ to XML, which presents the equivalent C# code.
`
`