Есть ли способ сериализации нескольких элементов X в одну строку? - PullRequest
12 голосов
/ 18 апреля 2011

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

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

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>

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

ВЫСТАВКА 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 вместо Приложения A. Этот текстовый блок станет частью большего числа элементов на странице XAML, поэтому часть .ToString не совсем точна в это местоположение - это происходит, когда весь XAML для страницы управления пользователя выгружается в файл.


РЕДАКТИРОВАТЬ (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, основываясь на посте Эрика Уайта , который, кажется, является хорошим началом для пробежек (не включая потенциальные <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

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

Ответы [ 5 ]

5 голосов
/ 09 мая 2011

Ключом к решению этой проблемы является написание рекурсивной функции, которая выполняет итерацию по дереву 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 , в котором представлен эквивалентный код 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

`

2 голосов
/ 07 мая 2011

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

Это использует преимущества LINQ to 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>
1 голос
/ 07 мая 2011

Вы можете использовать перегрузку ToString(), которая позволяет указать SaveOptions, чтобы вы могли отключить форматирование. Однако я не думаю, что вы можете установить параметры сохранения для отдельных XElements однако. Вы должны будете выписать строку вручную, как у вас уже есть. Вот частичная реализация, хотя она просто записывает все текстовые блоки в одну строку. Будет ли это приемлемым форматированием?

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>
0 голосов
/ 07 мая 2011

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

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)

enter image description here

0 голосов
/ 07 мая 2011

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

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

...