我正在处理Silverlight 3中可怕的<Run/>
,并且必须以编程方式创建<TextBlock>
及其内联:
为什么害怕?因为它不起作用,我想,你期望的方式。下面的图表A应该产生
BARN(每个角色都有奇特的颜色),但它会产生:
B A R N
<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>
产生预期结果的是:
<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>
愚蠢,是吗?无论如何,这是在空白处理下的XAML Processing Differences Between Silverlight 3 and Silverlight 4记录的文件,其中说:
Silverlight 3更多地处理空白 字面上在更广泛的范围内,包括 在某些情况下考虑CLRF 重大。这有时会导致 文件格式XAML,省略了CRLF 为了避免不必要的空白 介绍,但不是 在编辑中人类可读 环境。 Silverlight 4使用了 更直观的重要空白 与WPF类似的模型。这个 模型折叠文件格式 大多数情况下都是空白 某些CLR归因的例外情况 处理所有空格的容器 同样重要。这个空白模型 使编辑环境更好 引入空白的自由 可以改善XAML代码格式。 此外,Silverlight 4还具有文本元素 这允许更大的控制权 空白呈现问题。
很好,但我没有使用SL4,因为我正在以编程方式创建一个WP7应用程序。是的,我的XAML已生成。使用XML文字。然后发送到一个字符串。像这样:
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
毕竟,这是我的问题:如何制作附件B而不是附件A.该文本块将成为XAML页面中更多元素的一部分,因此.ToString
部分并不完全正确在此位置准确 - 当用户控制页面的所有XAML被踢出文件时发生。
我已经取得了一些进展,如下所示,但我正在遇到一个关于如何完成不寻常的拆分和处理XML以输出字符串的精神障碍。拿这个新例子来说:
<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>
我希望输出字符串为:
<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>
我一直在查看基于Eric White's post的XMLWriter
和XMLWriterSettings
,这似乎是运行的良好开端(不包括潜在的<LineBreak/>
但是,这也让我感到困惑)。像这样:
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
但是我在解决如何解析它以产生上面所需的输出方面还有一个很大的问题。
答案 0 :(得分:5)
解决此问题的关键是编写一个递归函数,该函数遍历XML树,将各种元素和属性写入专门创建的XmlWriter对象。有一个“外部”XmlWriter对象可以写入缩进的XML,还有一个“内部”XmlWriter对象可以写入非缩进的XML。
递归函数最初使用'外部'XmlWriter,编写缩进的XML,直到它看到TextBlock元素。当遇到TextBlock元素时,它会创建“内部”XmlWriter对象,将TextBlock元素的子元素写入其中。它还将空格写入“内部”XmlWriter。
当'内部'XmlWriter对象完成编写TextBlock元素时,编写的文本将使用WriteRaw方法写入'外部'XmlWriter。
这种方法的优点是没有XML的后处理。对后处理XML非常困难,并确保您已正确处理所有情况,包括CData节点中的任意文本等。所有XML都只使用XmlWriter类编写,从而确保始终编写有效的XML 。唯一的例外是使用WriteRaw方法编写的特制白色空间,它实现了所需的缩进行为。
一个关键点是'内部'XmlWriter对象的一致性级别设置为ConformanceLevel.Fragment,因为'内部'XmlWriter需要编写没有根元素的XML。
为了实现所需的Run元素格式(即,相邻的Run元素之间没有无关紧要的空格),代码使用GroupAdjacent扩展方法。前段时间,我在the GroupAdjacent extension method for VB上写了一篇博文。
使用指定的示例XML运行代码时,它会输出:
<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>
以下是VB.NET示例程序的完整列表。另外,我写了一篇博文Custom Formatting of XML using LINQ to XML,其中提供了等效的C#代码。
`
Imports System.Text
Imports System.Xml
Public Class GroupOfAdjacent(Of TElement, TKey)
Implements IEnumerable(Of TElement)
Private _key As TKey
Private _groupList As List(Of TElement)
Public Property GroupList() As List(Of TElement)
Get
Return _groupList
End Get
Set(ByVal value As List(Of TElement))
_groupList = value
End Set
End Property
Public ReadOnly Property Key() As TKey
Get
Return _key
End Get
End Property
Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of TElement) _
Implements System.Collections.Generic.IEnumerable(Of TElement).GetEnumerator
Return _groupList.GetEnumerator
End Function
Public Function GetEnumerator1() As System.Collections.IEnumerator _
Implements System.Collections.IEnumerable.GetEnumerator
Return _groupList.GetEnumerator
End Function
Public Sub New(ByVal key As TKey)
_key = key
_groupList = New List(Of TElement)
End Sub
End Class
Module Module1
<System.Runtime.CompilerServices.Extension()> _
Public Function GroupAdjacent(Of TElement, TKey)(ByVal source As IEnumerable(Of TElement), _
ByVal keySelector As Func(Of TElement, TKey)) As List(Of GroupOfAdjacent(Of TElement, TKey))
Dim lastKey As TKey = Nothing
Dim currentGroup As GroupOfAdjacent(Of TElement, TKey) = Nothing
Dim allGroups As List(Of GroupOfAdjacent(Of TElement, TKey)) = New List(Of GroupOfAdjacent(Of TElement, TKey))()
For Each item In source
Dim thisKey As TKey = keySelector(item)
If lastKey IsNot Nothing And Not thisKey.Equals(lastKey) Then
allGroups.Add(currentGroup)
End If
If Not thisKey.Equals(lastKey) Then
currentGroup = New GroupOfAdjacent(Of TElement, TKey)(keySelector(item))
End If
currentGroup.GroupList.Add(item)
lastKey = thisKey
Next
If lastKey IsNot Nothing Then
allGroups.Add(currentGroup)
End If
Return allGroups
End Function
Public Sub WriteStartElement(ByVal writer As XmlWriter, ByVal e As XElement)
Dim ns As XNamespace = e.Name.Namespace
writer.WriteStartElement(e.GetPrefixOfNamespace(ns), _
e.Name.LocalName, ns.NamespaceName)
For Each a In e.Attributes
ns = a.Name.Namespace
Dim localName As String = a.Name.LocalName
Dim namespaceName As String = ns.NamespaceName
writer.WriteAttributeString( _
e.GetPrefixOfNamespace(ns), _
localName, _
IIf(namespaceName.Length = 0 And localName = "xmlns", _
XNamespace.Xmlns.NamespaceName, namespaceName),
a.Value)
Next
End Sub
Public Sub WriteElement(ByVal writer As XmlWriter, ByVal e As XElement)
If (e.Name = "TextBlock") Then
WriteStartElement(writer, e)
writer.WriteRaw(Environment.NewLine)
' Create an XML writer that outputs no insignificant white space so that we can
' write to it and explicitly control white space.
Dim settings As XmlWriterSettings = New XmlWriterSettings()
settings.Indent = False
settings.OmitXmlDeclaration = True
settings.ConformanceLevel = ConformanceLevel.Fragment
Dim sb As StringBuilder = New StringBuilder()
Using newXmlWriter As XmlWriter = XmlWriter.Create(sb, settings)
' Group adjacent runs so that they can be output with no whitespace between them
Dim groupedRuns = e.Nodes().GroupAdjacent( _
Function(n) As Boolean?
If TypeOf n Is XElement Then
Dim element As XElement = n
If element.Name = "Run" Then
Return True
End If
Return False
End If
Return False
End Function)
For Each g In groupedRuns
If g.Key = True Then
' Write white space so that the line of Run elements is properly indented.
newXmlWriter.WriteRaw("".PadRight((e.Ancestors().Count() + 1) * 2))
For Each run In g
run.WriteTo(newXmlWriter)
Next
newXmlWriter.WriteRaw(Environment.NewLine)
Else
For Each g2 In g
' Write some white space so that each child element is properly indented.
newXmlWriter.WriteRaw("".PadRight((e.Ancestors().Count() + 1) * 2))
g2.WriteTo(newXmlWriter)
newXmlWriter.WriteRaw(Environment.NewLine)
Next
End If
Next
End Using
writer.WriteRaw(sb.ToString())
writer.WriteRaw("".PadRight(e.Ancestors().Count() * 2))
writer.WriteEndElement()
Else
WriteStartElement(writer, e)
For Each n In e.Nodes
If TypeOf n Is XElement Then
Dim element = n
WriteElement(writer, element)
Continue For
End If
n.WriteTo(writer)
Next
writer.WriteEndElement()
End If
End Sub
Function ToStringWithCustomWhiteSpace(ByVal element As XElement) As String
' Create XmlWriter that indents.
Dim settings As XmlWriterSettings = New XmlWriterSettings()
settings.Indent = True
settings.OmitXmlDeclaration = True
Dim sb As StringBuilder = New StringBuilder()
Using xmlWriter As XmlWriter = xmlWriter.Create(sb, settings)
WriteElement(xmlWriter, element)
End Using
Return sb.ToString()
End Function
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>
<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>
Console.Write(ToStringWithCustomWhiteSpace(myXML))
Console.ReadLine()
End Sub
End Module
`
答案 1 :(得分:2)
这是您可以尝试的另一种方法。与我所做的测试相比,效果非常好。
这利用了LINQ to XML和Regular Expressions。我们的想法是使用特制的注释来注释掉所有Run
元素并获取字符串表示。然后扫描查找连续的Run
元素并将它们“合并”在一起。然后,当完成后,“取消注释”所有Run
元素。
<Extension()>
Public Function ToXamlString(ByVal element As XElement) As String
Dim proxy As New XElement(element)
Dim rng As New Random
Dim seed1 = rng.Next
Dim seed2 = rng.Next
Dim query = proxy...<Run>
For Each run In query.ToList
run.ReplaceWith(New XComment(String.Concat(seed1, run, seed2)))
Next
Dim asStr = proxy.ToString
asStr = Regex.Replace(asStr, String.Concat(seed2, "-->\s+<!--", seed1), String.Empty, RegexOptions.Multiline)
Return Regex.Replace(asStr, String.Concat("<!--", seed1, "(<Run[\s\S]+?>)", seed2, "-->"), "$1", RegexOptions.Multiline)
End Function
输出:
<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>
答案 2 :(得分:1)
您可以使用允许您指定ToString()
的SaveOptions
重载,以便禁用格式设置。但是,我不认为您可以为个别XElements
设置保存选项。你必须像现有的那样手动写出字符串。这是一个部分实现,虽然它只是在一行写出TextBlocks。这是一种可接受的格式吗?
Public Function ToXamlString(element As XElement, indentLevel As Int32) As String
Dim sb As New StringBuilder()
Dim indent As New String(" "c, indentLevel * 2)
If element.Name = "TextBlock" Then
sb.Append(indent).AppendLine(element.ToString(SaveOptions.DisableFormatting))
Else
sb.Append(indent).AppendLine("<" & element.Name.ToString & ">")
For Each child In element.Elements
sb.Append(ToXamlString(child, indentLevel + 1))
Next
sb.Append(indent).AppendLine("</" & element.Name.ToString & ">")
End If
Return sb.ToString
End Function
''# call it
Console.WriteLine(ToXamlString(element, 0))
输出:
<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>
答案 3 :(得分:0)
我不确定我是否正在掌握你的VB.NET xml语法。但是最终你会自动生成Xaml,这是一个通过XamlParser推送的字符串。由于您使用代码生成Xaml,因此您无需在任何时候手动编辑结果。
因此,为什么不将最终字符串中的所有CR和LF字符从发送到XamlParser之前删除?
答案 4 :(得分:0)
我不知道我的问题是否正确,但这是一个我认为您正在寻找的例子:
<强> C#强>
Canvas _testCanvas = new Canvas();
string _testString = "<Canvas x:Name='testCanvas' xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'><Grid><StackPanel Orientation='Vertical'><TextBlock><Run Text='r'/><Run Text='u'/><Run Text='n'/></TextBlock><TextBlock><Run Text='far a'/><Run Text='way'/><Run Text=' from me'/></TextBlock></StackPanel></Grid></Canvas>";
_testCanvas = (Canvas)XamlReader.Load(_testString);
ContentPanel.Children.Add(_testCanvas);
VB.NET (我使用了developerfusion转换器,所以请耐心等待):
Dim _testCanvas As New Canvas()
Dim _testString As String = "<Canvas x:Name='testCanvas' xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'><Grid><StackPanel Orientation='Vertical'><TextBlock><Run Text='r'/><Run Text='u'/><Run Text='n'/></TextBlock><TextBlock><Run Text='far a'/><Run Text='way'/><Run Text=' from me'/></TextBlock></StackPanel></Grid></Canvas>"
_testCanvas = DirectCast(XamlReader.Load(_testString), Canvas)
ContentPanel.Children.Add(_testCanvas)