我正在处理可怕的 <Run/>
在 Silverlight 3 中,必须以编程方式创建 <TextBlock>
及其内联:
为什么害怕?因为它不起作用,我猜,你期望的方式。下面的图表 A 应该产生
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>
然而,产生预期结果的是:
展品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>
笨蛋吧?无论如何,这是记录在案的@ XAML Processing Differences Between Silverlight 3 and Silverlight 4下 空白处理 它说:
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.
很好,但我没有使用 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 被踢出到文件时就会发生这种情况。编辑(2011 年 5 月 6 日):一点点进步和赏金
我已经取得了一些进展,如下所示,但我在这里遇到了一个心理障碍,即如何完成异常的 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>
我一直在看
XMLWriter
和 XMLWriterSettings
, 基于 Eric White's post ,这对于运行来说似乎是一个好的开始(还不包括潜在的 <LineBreak/>
s,这也让我难堪)。像这样: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
但是我在弄清楚如何解析它以产生上面所需的输出时遇到了一个大问题。
最佳答案
解决这个问题的关键是编写一个递归函数,它遍历 XML 树,将各种元素和属性写入专门创建的 XmlWriter 对象。有一个写入缩进 XML 的“外部”XmlWriter 对象和一个写入非缩进 XML 的“内部”XmlWriter 对象。
递归函数最初使用“外部”XmlWriter,编写缩进的 XML,直到它看到 TextBlock 元素。当它遇到 TextBlock 元素时,它会创建“内部”XmlWriter 对象,将 TextBlock 元素的子元素写入其中。它还将空白写入“内部”XmlWriter。
当“内部”XmlWriter 对象完成编写 TextBlock 元素时,使用 WriteRaw 方法将编写器写入的文本写入“外部”XmlWriter。
这种方法的优点是无需对 XML 进行后处理。对 XML 进行后处理并确保您已正确处理所有情况非常困难,包括 CData 节点中的任意文本等。所有 XML 仅使用 XmlWriter 类编写,从而确保这将始终编写有效的 XML .唯一的异常(exception)是使用 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
`
关于vb.net - 有没有办法将多个 XElement 序列化到同一行上?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5698215/