Browse Source

Written custom XML parser which can handle malformed XML. Subsequent parsing is incremental to increase performance. The old and new parse trees are compared, the DOM is updated and user events are raised.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4578 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
David Srbecký 16 years ago
parent
commit
84bce20b8b
  1. 8
      samples/XmlDOM/App.xaml
  2. 19
      samples/XmlDOM/App.xaml.cs
  3. 31
      samples/XmlDOM/Properties/AssemblyInfo.cs
  4. 27
      samples/XmlDOM/Properties/WPFAssemblyInfo.cs
  5. 40
      samples/XmlDOM/Window1.xaml
  6. 82
      samples/XmlDOM/Window1.xaml.cs
  7. 65
      samples/XmlDOM/XmlDOM.csproj
  8. 18
      samples/XmlDOM/XmlDOM.sln
  9. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  10. 112
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/ObservableCollections.cs
  11. 455
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs
  12. 363
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/XmlParser.cs

8
samples/XmlDOM/App.xaml

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
<Application x:Class="XmlDOM.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<Application.Resources>
</Application.Resources>
</Application>

19
samples/XmlDOM/App.xaml.cs

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
using System;
using System.Windows;
using System.Data;
using System.Xml;
using System.Configuration;
namespace XmlDOM
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
InitializeComponent();
}
}
}

31
samples/XmlDOM/Properties/AssemblyInfo.cs

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
#region Using directives
using System;
using System.Reflection;
using System.Runtime.InteropServices;
#endregion
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("XmlDOM")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("XmlDOM")]
[assembly: AssemblyCopyright("Copyright 2009")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// This sets the default COM visibility of types in the assembly to invisible.
// If you need to expose a type to COM, use [ComVisible(true)] on that type.
[assembly: ComVisible(false)]
// The assembly version has following format :
//
// Major.Minor.Build.Revision
//
// You can specify all the values or you can use the default the Revision and
// Build Numbers by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")]

27
samples/XmlDOM/Properties/WPFAssemblyInfo.cs

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
#region Using directives
using System.Resources;
using System.Windows;
#endregion
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

40
samples/XmlDOM/Window1.xaml

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
<Window x:Class="XmlDOM.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clr="clr-namespace:ICSharpCode.AvalonEdit.XmlParser;assembly=ICSharpCode.AvalonEdit"
xmlns:ic="http://icsharpcode.net/sharpdevelop/avalonedit"
Title="XmlEditor" Height="450" Width="600"
>
<Window.Resources>
<Storyboard x:Key="anim">
<ColorAnimation Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)" To="Transparent" Duration="0:0:4"/>
</Storyboard>
<HierarchicalDataTemplate DataType="{x:Type clr:RawDocument}" ItemsSource="{Binding Helper_Elements}">
<TextBlock Text="XML Document" Margin="2"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type clr:RawElement}" ItemsSource="{Binding Helper_AttributesAndElements}">
<TextBlock Text="{Binding StartTag.Name}" Margin="2" Initialized="BindElement"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type clr:RawAttribute}">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding Name}" Foreground="Blue" Initialized="BindObject"/>
<TextBlock Text="=" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Value}" Foreground="Blue" Initialized="BindObject"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type clr:RawText}" ItemContainerStyle="{x:Null}">
<Border BorderBrush="LightGray" Height="1" BorderThickness="1"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ic:TextEditor x:Name="editor"/>
<DockPanel Grid.Column="1">
<Button DockPanel.Dock="Top" Content="Parse" Click="Button_Click"/>
<TreeView Name="treeView"/>
</DockPanel>
</Grid>
</Window>

82
samples/XmlDOM/Window1.xaml.cs

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="David Srbecký" email="dsrbecky@gmail.com"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.XmlParser;
namespace XmlDOM
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
XmlParser parser;
public Window1()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
parser = new XmlParser(editor.Document);
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(0.25);
timer.Tick += delegate { Button_Click(null, null); };
timer.Start();
base.OnInitialized(e);
}
void Button_Click(object sender, RoutedEventArgs e)
{
RawDocument doc = parser.Parse();
if (treeView.Items.Count == 0) {
treeView.Items.Add(doc);
}
}
void BindObject(object sender, EventArgs e)
{
TextBlock textBlock = (TextBlock)sender;
RawObject node = (RawObject)textBlock.DataContext;
node.LocalDataChanged += delegate {
BindingOperations.GetBindingExpression(textBlock, TextBlock.TextProperty).UpdateTarget();
textBlock.Background = new SolidColorBrush(Colors.LightGreen);
Storyboard sb = ((Storyboard)this.FindResource("anim"));
Storyboard.SetTarget(sb, textBlock);
sb.Begin();
};
}
void BindElement(object sender, EventArgs e)
{
TextBlock textBlock = (TextBlock)sender;
RawElement node = (RawElement)textBlock.DataContext;
node.StartTag.LocalDataChanged += delegate {
BindingOperations.GetBindingExpression(textBlock, TextBlock.TextProperty).UpdateTarget();
textBlock.Background = new SolidColorBrush(Colors.LightGreen);
Storyboard sb = ((Storyboard)this.FindResource("anim"));
Storyboard.SetTarget(sb, textBlock);
sb.Begin();
};
}
}
}

65
samples/XmlDOM/XmlDOM.csproj

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<ProjectGuid>{AF8CA20E-58AC-423E-95A8-5AA464938D19}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<OutputType>WinExe</OutputType>
<RootNamespace>XmlDOM</RootNamespace>
<AssemblyName>XmlDOM</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<AppDesignerFolder>Properties</AppDesignerFolder>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>bin\Debug\</OutputPath>
<DebugSymbols>True</DebugSymbols>
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>bin\Release\</OutputPath>
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Reference Include="ICSharpCode.AvalonEdit">
<HintPath>..\..\bin\ICSharpCode.AvalonEdit.dll</HintPath>
</Reference>
<Reference Include="PresentationCore">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
<Reference Include="PresentationFramework">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<SubType>Code</SubType>
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\WPFAssemblyInfo.cs" />
<Compile Include="Window1.xaml.cs">
<SubType>Code</SubType>
<DependentUpon>Window1.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Page Include="Window1.xaml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
</Project>

18
samples/XmlDOM/XmlDOM.sln

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 10
# SharpDevelop 4.0.0.4567
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XmlDOM", "XmlDOM.csproj", "{AF8CA20E-58AC-423E-95A8-5AA464938D19}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AF8CA20E-58AC-423E-95A8-5AA464938D19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF8CA20E-58AC-423E-95A8-5AA464938D19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF8CA20E-58AC-423E-95A8-5AA464938D19}.Release|Any CPU.Build.0 = Release|Any CPU
{AF8CA20E-58AC-423E-95A8-5AA464938D19}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
EndGlobal

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -304,6 +304,9 @@ @@ -304,6 +304,9 @@
<Compile Include="Utils\ThrowUtil.cs" />
<Compile Include="Utils\Win32.cs" />
<CodeAnalysisDictionary Include="Properties\CodeAnalysisDictionary.xml" />
<Compile Include="XmlParser\ObservableCollections.cs" />
<Compile Include="XmlParser\RawObjects.cs" />
<Compile Include="XmlParser\XmlParser.cs" />
<Resource Include="themes\RightArrow.cur" />
<EmbeddedResource Include="Highlighting\Resources\ASPX.xshd" />
<EmbeddedResource Include="Highlighting\Resources\BAT-Mode.xshd" />
@ -343,6 +346,7 @@ @@ -343,6 +346,7 @@
<Folder Include="Rendering" />
<Folder Include="Utils" />
<Folder Include="themes" />
<Folder Include="XmlParser" />
<Page Include="CodeCompletion\CompletionList.xaml" />
<Page Include="CodeCompletion\InsightWindow.xaml" />
<Page Include="TextEditor.xaml" />

112
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/ObservableCollections.cs

@ -0,0 +1,112 @@ @@ -0,0 +1,112 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="David Srbecký" email="dsrbecky@gmail.com"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
// Missing XML comment
#pragma warning disable 1591
namespace ICSharpCode.AvalonEdit.XmlParser
{
public class FilteredObservableCollection<T>: ObservableCollection<T>
{
ObservableCollection<T> source;
Predicate<T> condition;
List<int> srcPtrs = new List<int>();
public FilteredObservableCollection(ObservableCollection<T> source, Predicate<T> condition)
{
this.source = source;
this.condition = condition;
for(int i = 0; i < source.Count; i++) {
if (condition(source[i])) {
int index = srcPtrs.Count;
this.InsertItem(index, source[i]);
srcPtrs.Insert(index, i);
}
}
this.source.CollectionChanged += new NotifyCollectionChangedEventHandler(FilteredObservableCollection_CollectionChanged);
}
void FilteredObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove) {
// Remove the item from our collection
if (condition((T)e.OldItems[0])) {
int index = srcPtrs.IndexOf(e.OldStartingIndex);
this.RemoveAt(index);
srcPtrs.RemoveAt(index);
}
// Update pointers
for(int i = 0; i < srcPtrs.Count; i++) {
if (srcPtrs[i] > e.OldStartingIndex) {
srcPtrs[i]--;
}
}
}
if (e.Action == NotifyCollectionChangedAction.Add) {
// Update pointers
for(int i = 0; i < srcPtrs.Count; i++) {
if (srcPtrs[i] >= e.NewStartingIndex) {
srcPtrs[i]++;
}
}
// Add item to collection
if (condition((T)e.NewItems[0])) {
int index = srcPtrs.FindIndex(srcPtr => srcPtr >= e.NewStartingIndex);
if (index == -1) index = srcPtrs.Count;
this.InsertItem(index, (T)e.NewItems[0]);
srcPtrs.Insert(index, e.NewStartingIndex);
}
}
}
}
public class MergedObservableCollection<T>: ObservableCollection<T>
{
ObservableCollection<T> a;
ObservableCollection<T> b;
public MergedObservableCollection(ObservableCollection<T> a, ObservableCollection<T> b)
{
this.a = a;
this.b = b;
foreach(T item in a) this.Add(item);
foreach(T item in b) this.Add(item);
this.a.CollectionChanged += new NotifyCollectionChangedEventHandler(MergedObservableCollection_CollectionAChanged);
this.b.CollectionChanged += new NotifyCollectionChangedEventHandler(MergedObservableCollection_CollectionBChanged);
}
void MergedObservableCollection_CollectionAChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove) {
this.RemoveAt(e.OldStartingIndex);
}
if (e.Action == NotifyCollectionChangedAction.Add) {
this.InsertItem(e.NewStartingIndex, (T)e.NewItems[0]);
}
}
void MergedObservableCollection_CollectionBChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove) {
this.RemoveAt(e.OldStartingIndex + a.Count);
}
if (e.Action == NotifyCollectionChangedAction.Add) {
this.InsertItem(e.NewStartingIndex + a.Count, (T)e.NewItems[0]);
}
}
}
}

455
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs

@ -0,0 +1,455 @@ @@ -0,0 +1,455 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="David Srbecký" email="dsrbecky@gmail.com"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using ICSharpCode.AvalonEdit.Document;
// Missing XML comment
#pragma warning disable 1591
namespace ICSharpCode.AvalonEdit.XmlParser
{
public class RawObjectEventArgs: EventArgs
{
public RawObject Object { get; set; }
}
/// <summary>
/// The base class for all XML objects. The objects store the precise text
/// representation so that generated text will preciesly match original.
/// </summary>
public abstract class RawObject: TextSegment
{
/// <summary>
/// Unique identifier for the specific call of parsing read function.
/// It is used to uniquely identify all object data (including nested).
/// </summary>
internal object ReadCallID { get; private set; }
public RawObject Parent { get; set; }
public RawDocument Document {
get {
if (this.Parent != null) {
return this.Parent.Document;
} else if (this is RawDocument) {
return (RawDocument)this;
} else {
return null;
}
}
}
/// <summary> Occurs when the value of any local properties changes. Nested changes do not cause the event to occur </summary>
public event EventHandler LocalDataChanged;
protected void OnLocalDataChanged()
{
Log("XML DOM: Local data changed for {0}", this);
if (LocalDataChanged != null) {
LocalDataChanged(this, EventArgs.Empty);
}
}
public new int EndOffset {
get {
return this.StartOffset + this.Length;
}
set {
this.Length = value - this.StartOffset;
}
}
public RawObject()
{
this.ReadCallID = new object();
}
public RawObject Clone()
{
RawObject clone = (RawObject)System.Activator.CreateInstance(this.GetType());
bool oldVal = LoggingEnabled;
LoggingEnabled = false;
clone.UpdateDataFrom(this);
LoggingEnabled = oldVal;
return clone;
}
public virtual IEnumerable<RawObject> GetSeftAndAllNestedObjects()
{
yield return this;
}
public virtual void UpdateDataFrom(RawObject source)
{
this.ReadCallID = source.ReadCallID;
this.StartOffset = source.StartOffset;
this.EndOffset = source.EndOffset;
}
public override string ToString()
{
return string.Format("{0}({1}-{2})", this.GetType().Name.Remove(0, 3), this.StartOffset, this.EndOffset);
}
public static bool LoggingEnabled = true;
public static void Log(string format, params object[] args)
{
if (LoggingEnabled) {
System.Diagnostics.Debug.WriteLine(format, args);
}
}
internal void OnInserting(RawObject parent, int index)
{
Log("XML DOM: Inserting {0} at index {1}", this, index);
this.Parent = parent;
if (this.Document != null) {
foreach(RawObject obj in GetSeftAndAllNestedObjects()) {
this.Document.OnObjectAttached(new RawObjectEventArgs() { Object = obj });
}
}
}
internal void OnRemoving(RawObject parent, int index)
{
Log("XML DOM: Removing {0} at index {1}", this, index);
this.Parent = null;
if (this.Document != null) {
foreach(RawObject obj in GetSeftAndAllNestedObjects()) {
this.Document.OnObjectDettached(new RawObjectEventArgs() { Object = obj });
}
}
}
}
public class RawDocument: RawObject
{
public ObservableCollection<RawObject> Children { get; set; }
public event EventHandler<RawObjectEventArgs> ObjectAttached;
public event EventHandler<RawObjectEventArgs> ObjectDettached;
public ObservableCollection<RawObject> Helper_Elements {
get {
return new FilteredObservableCollection<RawObject>(this.Children, x => x is RawElement);
}
}
internal void OnObjectAttached(RawObjectEventArgs e)
{
if (ObjectAttached != null) ObjectAttached(this, e);
}
internal void OnObjectDettached(RawObjectEventArgs e)
{
if (ObjectDettached != null) ObjectDettached(this, e);
}
public RawDocument()
{
this.Children = new ObservableCollection<RawObject>();
}
public override IEnumerable<RawObject> GetSeftAndAllNestedObjects()
{
return Enumerable.Union(
new RawObject[] { this },
this.Children.SelectMany(x => x.GetSeftAndAllNestedObjects())
);
}
public override void UpdateDataFrom(RawObject source)
{
if (this.ReadCallID == source.ReadCallID) return;
base.UpdateDataFrom(source);
RawDocument src = (RawDocument)source;
RawUtils.SmartListUpdate(src.Children, this.Children, this);
}
public XDocument CreateXDocument(bool autoUpdate)
{
XDocument doc = new XDocument();
return doc;
}
public override string ToString()
{
return string.Format("[{0} Chld:{1}]", base.ToString(), this.Children.Count);
}
}
public class RawTag: RawObject
{
public string OpeningBracket { get; set; } // "<" or "</"
public string Name { get; set; }
public ObservableCollection<RawObject> Attributes { get; set; }
public string ClosingBracket { get; set; } // ">" or "/>" for well formed
public RawTag()
{
this.Attributes = new ObservableCollection<RawObject>();
}
public override IEnumerable<RawObject> GetSeftAndAllNestedObjects()
{
return Enumerable.Union(
new RawObject[] { this },
this.Attributes // Have no nested objects
);
}
public override void UpdateDataFrom(RawObject source)
{
if (this.ReadCallID == source.ReadCallID) return;
base.UpdateDataFrom(source);
RawTag src = (RawTag)source;
if (this.OpeningBracket != src.OpeningBracket ||
this.Name != src.Name ||
this.ClosingBracket != src.ClosingBracket)
{
this.OpeningBracket = src.OpeningBracket;
this.Name = src.Name;
this.ClosingBracket = src.ClosingBracket;
OnLocalDataChanged();
}
RawUtils.SmartListUpdate(src.Attributes, this.Attributes, this);
}
public override string ToString()
{
return string.Format("[{0} '{1}{2}{3}' Attr:{4}]", base.ToString(), this.OpeningBracket, this.Name, this.ClosingBracket, this.Attributes.Count);
}
}
public class RawElement: RawObject
{
public RawTag StartTag { get; set; }
public ObservableCollection<RawObject> Children { get; set; }
public bool HasEndTag { get; set; }
public RawTag EndTag { get; set; }
public ObservableCollection<RawObject> Helper_AttributesAndElements {
get {
return new MergedObservableCollection<RawObject>(
new FilteredObservableCollection<RawObject>(this.StartTag.Attributes, x => x is RawAttribute),
new FilteredObservableCollection<RawObject>(this.Children, x => x is RawElement)
);
}
}
public RawElement()
{
this.StartTag = new RawTag() { Parent = this };
this.Children = new ObservableCollection<RawObject>();
this.EndTag = new RawTag() { Parent = this };
}
public override IEnumerable<RawObject> GetSeftAndAllNestedObjects()
{
return new IEnumerable<RawObject>[] {
new RawObject[] { this },
this.StartTag.GetSeftAndAllNestedObjects(),
this.Children.SelectMany(x => x.GetSeftAndAllNestedObjects()),
this.EndTag.GetSeftAndAllNestedObjects()
}.SelectMany(x => x);
}
public override void UpdateDataFrom(RawObject source)
{
if (this.ReadCallID == source.ReadCallID) return;
base.UpdateDataFrom(source);
RawElement src = (RawElement)source;
this.StartTag.UpdateDataFrom(src.StartTag);
RawUtils.SmartListUpdate(src.Children, this.Children, this);
this.EndTag.UpdateDataFrom(src.EndTag);
}
public XElement CreateXElement(bool autoUpdate)
{
XElement elem = new XElement(string.Empty);
UpdateXElement(elem);
if (autoUpdate) this.StartTag.LocalDataChanged += delegate { UpdateXElement(elem); };
return elem;
}
void UpdateXElement(XElement elem)
{
elem.Name = this.StartTag.Name;
}
public override string ToString()
{
return string.Format("[{0} '{1}{2}{3}' Attr:{4} Chld:{5}]", base.ToString(), this.StartTag.OpeningBracket, this.StartTag.Name, this.StartTag.ClosingBracket, this.StartTag.Attributes.Count, this.Children.Count);
}
}
public class RawAttribute: RawObject
{
public string Name { get; set; }
public string EqualsSign { get; set; }
public string Value { get; set; }
public override void UpdateDataFrom(RawObject source)
{
if (this.ReadCallID == source.ReadCallID) return;
base.UpdateDataFrom(source);
RawAttribute src = (RawAttribute)source;
if (this.Name != src.Name ||
this.EqualsSign != src.EqualsSign ||
this.Value != src.Value)
{
this.Name = src.Name;
this.EqualsSign = src.EqualsSign;
this.Value = src.Value;
OnLocalDataChanged();
}
}
public XAttribute CreateXAttribute(bool autoUpdate)
{
XAttribute attr = new XAttribute(string.Empty, string.Empty);
UpdateXAttribute(attr);
if (autoUpdate) this.LocalDataChanged += delegate { UpdateXAttribute(attr); };
return attr;
}
void UpdateXAttribute(XAttribute attr)
{
attr.Value = this.Value;
}
public override string ToString()
{
return string.Format("[{0} '{1}{2}{3}']", base.ToString(), this.Name, this.EqualsSign, this.Value);
}
}
public class RawText: RawObject
{
public string Value { get; set; }
public override void UpdateDataFrom(RawObject source)
{
if (this.ReadCallID == source.ReadCallID) return;
base.UpdateDataFrom(source);
RawText src = (RawText)source;
if (this.Value != src.Value) {
this.Value = src.Value;
OnLocalDataChanged();
}
}
public XText CreateXText(bool autoUpdate)
{
XText text = new XText(string.Empty);
UpdateXText(text);
if (autoUpdate) this.LocalDataChanged += delegate { UpdateXText(text); };
return text;
}
void UpdateXText(XText text)
{
text.Value = this.Value;
}
public override string ToString()
{
return string.Format("[{0} Text.Length={1}]", base.ToString(), this.Value.Length);
}
}
public static class RawUtils
{
/// <summary>
/// Copy items from source list over to destination list.
/// Prefer updating items with matching offsets.
/// </summary>
public static void SmartListUpdate(IList<RawObject> srcList, IList<RawObject> dstList, RawObject dstListOwner)
{
// Items up to 'i' shall be matching
for(int i = 0; i < srcList.Count;) {
// Item is missing - 'i' is invalid index
if (i >= dstList.Count) {
RawObject clone = srcList[i].Clone();
clone.OnInserting(dstListOwner, i);
dstList.Insert(i, clone);
i++; continue;
}
RawObject srcItem = srcList[i];
RawObject dstItem = dstList[i];
// Matching and updated
if (srcItem.ReadCallID == dstItem.ReadCallID) {
i++; continue;
}
// Offsets and types are matching
if (srcItem.StartOffset == dstItem.StartOffset &&
srcItem.GetType() == dstItem.GetType())
{
dstItem.UpdateDataFrom(srcItem);
i++; continue;
}
// Try to be smart by inserting or removing items
// Dst offset matches with future src
for(int srcItemIndex = i; srcItemIndex < srcList.Count; srcItemIndex++) {
RawObject src = srcList[srcItemIndex];
if (src.StartOffset == dstItem.StartOffset && src.GetType() == dstItem.GetType()) {
for(int j = i; j < srcItemIndex; j++) {
RawObject clone = srcList[j].Clone();
clone.OnInserting(dstListOwner, j);
dstList.Insert(j, clone);
}
i = srcItemIndex;
goto continue2;
}
}
// Scr offset matches with future dst
for(int dstItemIndex = i; dstItemIndex < dstList.Count; dstItemIndex++) {
RawObject dst = dstList[dstItemIndex];
if (srcItem.StartOffset == dst.StartOffset && srcItem.GetType() == dst.GetType()) {
for(int j = 0; j < dstItemIndex - i; j++) {
dstList[i].OnRemoving(dstListOwner, i);
dstList.RemoveAt(i);
}
goto continue2;
}
}
// No matches found - just update
if (dstItem.GetType() == srcItem.GetType()) {
dstItem.UpdateDataFrom(srcItem);
i++; continue;
}
// Remove whitespace in hope that element update will occur next
if (dstItem is RawText) {
dstList[i].OnRemoving(dstListOwner, i);
dstList.RemoveAt(i);
continue;
}
// Otherwise just add the item
{
RawObject clone = srcList[i].Clone();
clone.OnInserting(dstListOwner, i);
dstList.Insert(i, clone);
i++; continue;
}
}
// Remove extra items
while(dstList.Count > srcList.Count) {
dstList[srcList.Count].OnRemoving(dstListOwner, srcList.Count);
dstList.RemoveAt(srcList.Count);
}
// Continue for inner loops
continue2:;
}
}
}

363
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/XmlParser.cs

@ -0,0 +1,363 @@ @@ -0,0 +1,363 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="David Srbecký" email="dsrbecky@gmail.com"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.AvalonEdit.Document;
// Missing XML comment
#pragma warning disable 1591
namespace ICSharpCode.AvalonEdit.XmlParser
{
public class XmlParser
{
RawDocument userDocument;
TextDocument textDocument;
TextSegmentCollection<RawObject> userDom;
TextSegmentCollection<RawObject> parsedDom;
public XmlParser(TextDocument textDocument)
{
this.userDocument = new RawDocument();
this.textDocument = textDocument;
this.userDom = new TextSegmentCollection<RawObject>(textDocument);
this.parsedDom = new TextSegmentCollection<RawObject>(textDocument);
this.textDocument.Changed += TextDocument_Changed;
this.userDocument.ObjectAttached += delegate(object sender, RawObjectEventArgs e) {
this.userDom.Add(e.Object);
};
this.userDocument.ObjectDettached += delegate(object sender, RawObjectEventArgs e) {
this.userDom.Remove(e.Object);
};
}
public RawDocument Parse()
{
currentLocation = 0;
input = textDocument.Text;
RawDocument parsedDocument = ReadDocument();
userDocument.UpdateDataFrom(parsedDocument);
return userDocument;
}
void TextDocument_Changed(object sender, DocumentChangeEventArgs e)
{
int start = e.Offset - 2;
int end = e.Offset + e.InsertionLength + 2;
start = Math.Max(Math.Min(start, textDocument.TextLength - 1), 0);
end = Math.Max(Math.Min(end, textDocument.TextLength - 1), 0);
foreach(RawObject obj in parsedDom.FindOverlappingSegments(start, end - start)) {
parsedDom.Remove(obj);
Log("Removed cached item: {0}", obj);
}
}
T ReadFromCache<T>(int location) where T: RawObject
{
RawObject obj = parsedDom.FindFirstSegmentWithStartAfter(location);
while(obj != null && obj.StartOffset == location) {
if (obj is T) {
currentLocation += obj.Length;
return (T)obj;
}
obj = parsedDom.GetNextSegment(obj);
}
return null;
}
void Log(string text, params object[] pars)
{
System.Diagnostics.Debug.WriteLine("XML Parser: " + text, pars);
}
void LogParsed(RawObject obj)
{
System.Diagnostics.Debug.WriteLine("XML Parser: Parsed " + obj.ToString());
}
string input;
int currentLocation;
bool IsEndOfFile()
{
return currentLocation == input.Length;
}
bool HasMoreData()
{
return currentLocation < input.Length;
}
bool TryRead(char c)
{
if (currentLocation == input.Length) return false;
if (input[currentLocation] == c) {
currentLocation++;
return true;
} else {
return false;
}
}
bool TryPeek(char c)
{
if (currentLocation == input.Length) return false;
return input[currentLocation] == c;
}
bool TryPeek(string text)
{
if (currentLocation + text.Length > input.Length) return false;
return input.Substring(currentLocation, text.Length) == text;
}
bool TryMoveTo(params char[] c)
{
while(true) {
if (currentLocation == input.Length) return false;
if (c.Contains(input[currentLocation])) return true;
currentLocation++;
}
}
string GetText(int start, int end)
{
if (start == input.Length && end == input.Length) {
return string.Empty;
} else {
return input.Substring(start, end - start);
}
}
static char[] WhiteSpaceChars = new char[] {' ', '\n', '\r', '\t'};
static char[] WhiteSpaceAndReservedChars = new char[] {' ', '\n', '\r', '\t', '<', '=', '>', '/'};
bool? IsWhiteSpace()
{
if (currentLocation == input.Length) {
return null;
} else {
return WhiteSpaceChars.Contains(input[currentLocation]);
}
}
bool? IsWhiteSpaceOrReserved()
{
if (currentLocation == input.Length) {
return null;
} else {
return WhiteSpaceAndReservedChars.Contains(input[currentLocation]);
}
}
string ReadName()
{
Debug.Assert(HasMoreData());
int start = currentLocation;
TryMoveTo(WhiteSpaceAndReservedChars.ToArray());
return GetText(start, currentLocation);
}
RawDocument ReadDocument()
{
RawDocument doc = ReadFromCache<RawDocument>(currentLocation);
if (doc != null) return doc;
doc = new RawDocument();
doc.StartOffset = currentLocation;
while(true) {
if (IsEndOfFile()) {
break;
} else if (TryPeek('<')) {
doc.Children.Add(ReadElement(doc));
} else {
doc.Children.Add(ReadCharacterData(doc));
}
}
doc.EndOffset = currentLocation;
LogParsed(doc);
parsedDom.Add(doc);
return doc;
}
RawElement ReadElement(RawObject parent)
{
Debug.Assert(HasMoreData() && TryPeek('<'));
RawElement element = ReadFromCache<RawElement>(currentLocation);
if (element != null) return element;
element = new RawElement() { Parent = parent };
element.StartOffset = currentLocation;
element.StartTag = ReadTag(element);
// Read content
if (element.StartTag.ClosingBracket == ">") {
while(true) {
if (IsEndOfFile()) {
break;
} else if (TryPeek('<')) {
if (TryPeek("</")) break;
element.Children.Add(ReadElement(element));
} else {
element.Children.Add(ReadCharacterData(element));
}
}
}
// Read closing tag
if (TryPeek("</")) {
element.HasEndTag = true;
element.EndTag = ReadTag(element);
}
element.EndOffset = currentLocation;
LogParsed(element);
parsedDom.Add(element);
return element;
}
RawTag ReadTag(RawObject parent)
{
Debug.Assert(HasMoreData() && TryPeek('<'));
RawTag tag = ReadFromCache<RawTag>(currentLocation);
if (tag != null) return tag;
tag = new RawTag() { Parent = parent };
tag.StartOffset = currentLocation;
if (TryRead('<')) {
tag.OpeningBracket = "<";
if (TryRead('/')) {
tag.OpeningBracket += "/";
}
}
if (HasMoreData()) {
tag.Name = ReadName();
}
// Read attributes
while(true) {
if (IsWhiteSpace() == true) {
tag.Attributes.Add(ReadWhiteSpace(tag));
}
if (TryRead('>')) {
tag.ClosingBracket = ">";
break;
} else if (TryRead('/')) {
tag.ClosingBracket = "/";
if (TryRead('>')) {
tag.ClosingBracket += ">";
}
break;
}
if (TryPeek('<')) break;
if (HasMoreData()) {
tag.Attributes.Add(ReadAttribulte(tag));
continue;
}
break;
}
tag.EndOffset = currentLocation;
LogParsed(tag);
parsedDom.Add(tag);
return tag;
}
RawText ReadWhiteSpace(RawObject parent)
{
Debug.Assert(HasMoreData() && IsWhiteSpace() == true);
RawText ws = ReadFromCache<RawText>(currentLocation);
if (ws != null) return ws;
ws = new RawText() { Parent = parent };
ws.StartOffset = currentLocation;
int start = currentLocation;
while(IsWhiteSpace() == true) currentLocation++;
ws.Value = GetText(start, currentLocation);
ws.EndOffset = currentLocation;
parsedDom.Add(ws);
return ws;
}
RawAttribute ReadAttribulte(RawObject parent)
{
Debug.Assert(HasMoreData());
RawAttribute attr = ReadFromCache<RawAttribute>(currentLocation);
if (attr != null) return attr;
attr = new RawAttribute() { Parent = parent };
attr.StartOffset = currentLocation;
if (HasMoreData()) {
attr.Name = ReadName();
}
int checkpoint = currentLocation;
attr.EqualsSign = string.Empty;
if (IsWhiteSpace() == true) attr.EqualsSign += ReadWhiteSpace(attr).Value;
if (TryRead('=')) {
attr.EqualsSign += "=";
if (IsWhiteSpace() == true) attr.EqualsSign += ReadWhiteSpace(attr).Value;
if (IsWhiteSpaceOrReserved() == false) {
// Read attribute value
int start = currentLocation;
if (TryRead('"')) {
TryMoveTo('"', '<');
TryRead('"');
attr.Value = GetText(start, currentLocation);
} else if (TryRead('\'')) {
TryMoveTo('\'', '<');
TryRead('\'');
attr.Value = GetText(start, currentLocation);
} else {
attr.Value = ReadName();
}
}
} else {
attr.EqualsSign = null;
currentLocation = checkpoint;
}
attr.EndOffset = currentLocation;
parsedDom.Add(attr);
return attr;
}
RawText ReadCharacterData(RawObject parent)
{
Debug.Assert(HasMoreData());
RawText charData = ReadFromCache<RawText>(currentLocation);
if (charData != null) return charData;
charData = new RawText() { Parent = parent };
charData.StartOffset = currentLocation;
int start = currentLocation;
TryMoveTo('<');
charData.Value = GetText(start, currentLocation);
charData.EndOffset = currentLocation;
parsedDom.Add(charData);
return charData;
}
}
}
Loading…
Cancel
Save