vb.net - 有没有办法将多个 XElement 序列化到同一行上?

标签 vb.net silverlight silverlight-3.0 linq-to-xml whitespace

我正在处理可怕的 <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>

我一直在看XMLWriterXMLWriterSettings , 基于 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/

相关文章:

mysql - 与 MySql 中的数据库的 Visual Basic 连接

mysql - 循环在 Sub 中不断重复

silverlight - Silverlight 的性能和诊断工具

c# - 是否可以在 Windows Phone 7 文本框中放置 "hints"?

c# - 如何在WP7应用程序中显示EULA?

collections - 在Silverlight 2中渲染 View 模型的异构集合

c# - 一次更新 MVC 页面的不同部分?

vb.net - 通过 VB.NET 合并 excel 单元格,而不询问每个单元格何时有值

events - MouseLeave LostFocus 事件 Silverlight

silverlight - 如何从 silverlight 中单击的按钮中删除边框?