Есть ли способ сериализовать несколько XElements в одну строку?
я имею дело с ужасной <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>
что дает желаемый результат, однако:
выставка Б
<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
после всего этого, вот мой вопрос: Как я могу создать Exhibit B вместо Exhibit A. Этот textblock станет частью большего количества элементов на странице 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
на основе сообщение Эрика Уайта, что кажется хорошим началом для пробегов (не включая потенциал <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
но у меня есть огромная проблема, идущая намного дальше с выяснением того, как разобрать это, чтобы получить желаемый результат выше.
5 ответов
ключом к решению этой проблемы является запись рекурсивной функции, которая выполняет итерацию по дереву 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.Фрагмент, потому что "внутренний" 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
`
вот еще один подход, вы можете попробовать. Невероятно хорошо работает с тестами, которые я сделал.
Это использует преимущества 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, "-->"), "", 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>
вы можете использовать перегрузку 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>
Я не уверен, что понимаю ваши VB.NET синтаксис xml. Однако, в конечном счете, вы автоматически генерирующих код XAML, который fundementally строку, которую пихают через XamlParser. Поскольку вы создаете Xaml с кодом, вам не нужно будет редактировать результаты вручную в любой момент.
следовательно, почему бы просто не удалить все символы CR и LF из последней строки перед отправкой ее в XamlParser?
Я не знаю, правильно ли я понял ваш вопрос, но вот пример, который я думаю, что вы ищете:
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)