Подтвердить что ты не робот

Есть ли способ сериализации нескольких XElements на одну строку?

Я имею дело с ужасным <Run/> в Silverlight 3 и должен программно создать <TextBlock> и его встроенные строки:

Зачем бояться? Потому что это не работает, я думаю, так, как вы ожидали. В приведенной ниже таблице A должно быть указано

BARN
(с причудливыми цветами для каждого символа), но вместо этого оно выражается:
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>

Что дает желаемый результат, тем не менее:

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>

Глупый, а? Во всяком случае, это документировано @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

В конце концов, вот мой вопрос: как я могу сделать приложение B вместо Exhibit A. Этот текстовый блок станет частью большего количества элементов на странице XAML, поэтому часть .ToString не совсем точна в это место - это происходит, когда все XAML для пользовательской страницы управления выгружаются в файл.


EDIT (6 мая 2011 г.): небольшой прогресс и щедрость


Я сделал немного прогресса, как показано ниже, но я столкнулся с ментальным блоком здесь о том, как выполнить необычный раскол и обработку 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 post, который, кажется, хороший старт для пробегов (не включая потенциал <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

Но у меня возникла огромная проблема, которая идет гораздо дальше с выяснением того, как разобрать это, чтобы вывести желаемый результат выше.

4b9b3361

Ответ 1

Ключом к решению этой проблемы является запись рекурсивной функции, которая выполняет итерацию через дерево XML, записывая различные элементы и атрибуты специально созданным объектам XmlWriter. Существует внешний объект XmlWriter, который записывает отступы XML и внутренний объект XmlWriter, который записывает неизолированные XML.

Рекурсивная функция изначально использует "внешний" XmlWriter, записывая отступы XML, пока не увидит элемент TextBlock. Когда он сталкивается с элементом TextBlock, он создает "внутренний" объект XmlWriter, записывая ему дочерние элементы элемента TextBlock. Он также записывает пробел в "внутренний" XmlWriter.

Когда "внутренний" объект XmlWriter заканчивается написанием элемента TextBlock, текст, который написал автор, записывается в "внешний" XmlWriter с использованием метода WriteRaw.

Преимущества этого подхода заключаются в том, что пост-обработка XML отсутствует. Крайне сложно выполнить пост-обработку XML и быть уверенным, что вы правильно обрабатывали все случаи, включая произвольный текст в узлах CData и т.д. Весь XML написан с использованием только класса XmlWriter, тем самым гарантируя, что это всегда будет писать допустимый XML, Единственным исключением является специально созданное белое пространство, которое записывается с использованием метода WriteRaw, который обеспечивает желаемое поведение отступов.

Один из ключевых моментов заключается в том, что для внутреннего уровня XmlWriter для уровня соответствия соответствует значение ConformanceLevel.Fragment, потому что "внутренний" XmlWriter должен писать XML, который не имеет корневого элемента.

Для достижения желаемого форматирования элементов Run (т.е. смежные элементы Run не имеют между ними незначительного пробела), код использует метод расширения GroupAdjacent. Некоторое время назад я пишу сообщение в блоге метод расширения GroupAdjacent для 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. Кроме того, я написал сообщение в блоге Пользовательское форматирование XML с использованием LINQ to XML, в котором представлен эквивалентный код С#.

`

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= to'/>
                        <LineBreak/>
                        <Run Text=' work'/>
                    </TextBlock>
                </Grid>
            </Canvas>
        Console.Write(ToStringWithCustomWhiteSpace(myXML))
        Console.ReadLine()
    End Sub

End Module

`

Ответ 2

Вот еще один подход, который вы можете попробовать. Работает невероятно хорошо с тестами, которые я сделал.

Это использует LINQ для XML и регулярных выражений. Идея состоит в том, чтобы прокомментировать все элементы 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>

Ответ 3

Вы можете использовать перегрузку 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>

Ответ 4

Я не уверен, что я разбираюсь в синтаксисе VB.NET xml. Однако, в конечном счете, вы автоматически генерируете Xaml, который является фундаментально строкой, которая перемещается через XamlParser. Поскольку вы генерируете Xaml с кодом, вам не нужно будет вручную редактировать результаты в любой момент.

Следовательно, почему бы не просто удалить все символы CR и LF из последней строки перед отправкой в ​​XamlParser?

Ответ 5

Я не знаю, правильно ли получил ваш вопрос, но вот пример, который, я думаю, ваш поиск:

С#

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)

enter image description here