有没有办法将多个XElements序列化到同一行?

Tod*_*ain 12 vb.net silverlight whitespace linq-to-xml silverlight-3.0

我正在处理可怕<Run/>的Silverlight 3,并且必须以编程方式创建一个<TextBlock>及其内联:

为什么害怕?因为它不起作用,我猜,你期望的方式.下面的图表A应该生产

BARN
(每个角色都有奇特的颜色),但它会产生:
B A R N

附件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>
Run Code Online (Sandbox Code Playgroud)

然而,产生所需结果的是:

附件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>
Run Code Online (Sandbox Code Playgroud)

笨蛋,嗯?无论如何,这是在Whitespace处理下记录的@XAML处理Silverlight 3和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
Run Code Online (Sandbox Code Playgroud)

毕竟,这是我的问题:如何制作附件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>
Run Code Online (Sandbox Code Playgroud)

我希望输出字符串是:

<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>
Run Code Online (Sandbox Code Playgroud)

我一直在寻找的XMLWriterXMLWriterSettings的基础上,埃里克·怀特的职位,这似乎是一个好的开始运行(不包括潜在的<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
Run Code Online (Sandbox Code Playgroud)

但是我在解决如何解析它以产生上面所需的输出时会遇到很大的问题.

Eri*_*ite 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元素格式(即,运行相邻的元素之间没有无关紧要的空格),代码使用GroupAdjacent扩展方法.前段时间,我在VB的GroupAdjacent扩展方法上写了一篇博客文章.

使用指定的示例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>
Run Code Online (Sandbox Code Playgroud)

以下是VB.NET示例程序的完整列表.另外,我写了一篇博文,使用LINQ to XML定制格式化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
Run Code Online (Sandbox Code Playgroud)

`