24 changed files with 0 additions and 3371 deletions
@ -1,129 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Name-value pair in a tag
|
|
||||||
/// </summary>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] |
|
||||||
public class AXmlAttribute: AXmlObject |
|
||||||
{ |
|
||||||
/// <summary> Name with namespace prefix - exactly as in source file </summary>
|
|
||||||
public string Name { get; internal set; } |
|
||||||
/// <summary> Equals sign and surrounding whitespace </summary>
|
|
||||||
public string EqualsSign { get; internal set; } |
|
||||||
/// <summary> The raw value - exactly as in source file (*probably* quoted and escaped) </summary>
|
|
||||||
public string QuotedValue { get; internal set; } |
|
||||||
/// <summary> Unquoted and dereferenced value of the attribute </summary>
|
|
||||||
public string Value { get; internal set; } |
|
||||||
|
|
||||||
internal override void DebugCheckConsistency(bool checkParentPointers) |
|
||||||
{ |
|
||||||
DebugAssert(Name != null, "Null Name"); |
|
||||||
DebugAssert(EqualsSign != null, "Null EqualsSign"); |
|
||||||
DebugAssert(QuotedValue != null, "Null QuotedValue"); |
|
||||||
DebugAssert(Value != null, "Null Value"); |
|
||||||
base.DebugCheckConsistency(checkParentPointers); |
|
||||||
} |
|
||||||
|
|
||||||
#region Helpper methods
|
|
||||||
|
|
||||||
/// <summary> The element containing this attribute </summary>
|
|
||||||
/// <returns> Null if orphaned </returns>
|
|
||||||
public AXmlElement ParentElement { |
|
||||||
get { |
|
||||||
AXmlTag tag = this.Parent as AXmlTag; |
|
||||||
if (tag != null) { |
|
||||||
return tag.Parent as AXmlElement; |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> The part of name before ":"</summary>
|
|
||||||
/// <returns> Empty string if not found </returns>
|
|
||||||
public string Prefix { |
|
||||||
get { |
|
||||||
return GetNamespacePrefix(this.Name); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> The part of name after ":" </summary>
|
|
||||||
/// <returns> Whole name if ":" not found </returns>
|
|
||||||
public string LocalName { |
|
||||||
get { |
|
||||||
return GetLocalName(this.Name); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolved namespace of the name. Empty string if not found
|
|
||||||
/// From the specification: "The namespace name for an unprefixed attribute name always has no value."
|
|
||||||
/// </summary>
|
|
||||||
public string Namespace { |
|
||||||
get { |
|
||||||
if (string.IsNullOrEmpty(this.Prefix)) return NoNamespace; |
|
||||||
|
|
||||||
AXmlElement elem = this.ParentElement; |
|
||||||
if (elem != null) { |
|
||||||
return elem.ResolvePrefix(this.Prefix); |
|
||||||
} |
|
||||||
return NoNamespace; // Orphaned attribute
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Attribute is declaring namespace ("xmlns" or "xmlns:*") </summary>
|
|
||||||
public bool IsNamespaceDeclaration { |
|
||||||
get { |
|
||||||
return this.Name == "xmlns" || this.Prefix == "xmlns"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void AcceptVisitor(IAXmlVisitor visitor) |
|
||||||
{ |
|
||||||
visitor.VisitAttribute(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal override bool UpdateDataFrom(AXmlObject source) |
|
||||||
{ |
|
||||||
if (!base.UpdateDataFrom(source)) return false; |
|
||||||
AXmlAttribute src = (AXmlAttribute)source; |
|
||||||
if (this.Name != src.Name || |
|
||||||
this.EqualsSign != src.EqualsSign || |
|
||||||
this.QuotedValue != src.QuotedValue || |
|
||||||
this.Value != src.Value) |
|
||||||
{ |
|
||||||
OnChanging(); |
|
||||||
this.Name = src.Name; |
|
||||||
this.EqualsSign = src.EqualsSign; |
|
||||||
this.QuotedValue = src.QuotedValue; |
|
||||||
this.Value = src.Value; |
|
||||||
OnChanged(); |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}{2}{3}']", base.ToString(), this.Name, this.EqualsSign, this.Value); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,119 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Specailized attribute collection with attribute name caching
|
|
||||||
/// </summary>
|
|
||||||
public class AXmlAttributeCollection: FilteredCollection<AXmlAttribute, AXmlObjectCollection<AXmlObject>> |
|
||||||
{ |
|
||||||
/// <summary> Empty unbound collection </summary>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", |
|
||||||
Justification = "InsertItem prevents modifying the Empty collection")] |
|
||||||
public static readonly AXmlAttributeCollection Empty = new AXmlAttributeCollection(); |
|
||||||
|
|
||||||
/// <summary> Create unbound collection </summary>
|
|
||||||
protected AXmlAttributeCollection() {} |
|
||||||
|
|
||||||
/// <summary> Wrap the given collection. Non-attributes are filtered </summary>
|
|
||||||
public AXmlAttributeCollection(AXmlObjectCollection<AXmlObject> source): base(source) {} |
|
||||||
|
|
||||||
/// <summary> Wrap the given collection. Non-attributes are filtered. Items not matching the condition are filtered. </summary>
|
|
||||||
public AXmlAttributeCollection(AXmlObjectCollection<AXmlObject> source, Predicate<object> condition): base(source, condition) {} |
|
||||||
|
|
||||||
Dictionary<string, List<AXmlAttribute>> hashtable = new Dictionary<string, List<AXmlAttribute>>(); |
|
||||||
|
|
||||||
void AddToHashtable(AXmlAttribute attr) |
|
||||||
{ |
|
||||||
string localName = attr.LocalName; |
|
||||||
if (!hashtable.ContainsKey(localName)) { |
|
||||||
hashtable[localName] = new List<AXmlAttribute>(1); |
|
||||||
} |
|
||||||
hashtable[localName].Add(attr); |
|
||||||
} |
|
||||||
|
|
||||||
void RemoveFromHashtable(AXmlAttribute attr) |
|
||||||
{ |
|
||||||
string localName = attr.LocalName; |
|
||||||
hashtable[localName].Remove(attr); |
|
||||||
} |
|
||||||
|
|
||||||
static List<AXmlAttribute> NoAttributes = new List<AXmlAttribute>(); |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all attributes with given local name.
|
|
||||||
/// Hash table is used for lookup so this is cheap.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<AXmlAttribute> GetByLocalName(string localName) |
|
||||||
{ |
|
||||||
if (hashtable.ContainsKey(localName)) { |
|
||||||
return hashtable[localName]; |
|
||||||
} else { |
|
||||||
return NoAttributes; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void ClearItems() |
|
||||||
{ |
|
||||||
foreach(AXmlAttribute item in this) { |
|
||||||
RemoveFromHashtable(item); |
|
||||||
item.Changing -= item_Changing; |
|
||||||
item.Changed -= item_Changed; |
|
||||||
} |
|
||||||
base.ClearItems(); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void InsertItem(int index, AXmlAttribute item) |
|
||||||
{ |
|
||||||
// prevent insertions into the static 'Empty' instance
|
|
||||||
if (this == Empty) |
|
||||||
throw new NotSupportedException("Cannot insert into AXmlAttributeCollection.Empty"); |
|
||||||
|
|
||||||
AddToHashtable(item); |
|
||||||
item.Changing += item_Changing; |
|
||||||
item.Changed += item_Changed; |
|
||||||
base.InsertItem(index, item); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void RemoveItem(int index) |
|
||||||
{ |
|
||||||
RemoveFromHashtable(this[index]); |
|
||||||
this[index].Changing -= item_Changing; |
|
||||||
this[index].Changed -= item_Changed; |
|
||||||
base.RemoveItem(index); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void SetItem(int index, AXmlAttribute item) |
|
||||||
{ |
|
||||||
RemoveFromHashtable(this[index]); |
|
||||||
this[index].Changing -= item_Changing; |
|
||||||
this[index].Changed -= item_Changed; |
|
||||||
|
|
||||||
AddToHashtable(item); |
|
||||||
item.Changing += item_Changing; |
|
||||||
item.Changed += item_Changed; |
|
||||||
base.SetItem(index, item); |
|
||||||
} |
|
||||||
|
|
||||||
// Every item in the collection should be registered to these handlers
|
|
||||||
// so that we can handle renames
|
|
||||||
|
|
||||||
void item_Changing(object sender, AXmlObjectEventArgs e) |
|
||||||
{ |
|
||||||
RemoveFromHashtable((AXmlAttribute)e.Object); |
|
||||||
} |
|
||||||
|
|
||||||
void item_Changed(object sender, AXmlObjectEventArgs e) |
|
||||||
{ |
|
||||||
AddToHashtable((AXmlAttribute)e.Object); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,282 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Abstact base class for all types that can contain child nodes
|
|
||||||
/// </summary>
|
|
||||||
public abstract class AXmlContainer: AXmlObject |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Children of the node. It is read-only.
|
|
||||||
/// Note that is has CollectionChanged event.
|
|
||||||
/// </summary>
|
|
||||||
public AXmlObjectCollection<AXmlObject> Children { get; private set; } |
|
||||||
|
|
||||||
/// <summary> Create new container </summary>
|
|
||||||
protected AXmlContainer() |
|
||||||
{ |
|
||||||
this.Children = new AXmlObjectCollection<AXmlObject>(); |
|
||||||
} |
|
||||||
|
|
||||||
#region Helpper methods
|
|
||||||
|
|
||||||
ObservableCollection<AXmlElement> elements; |
|
||||||
|
|
||||||
/// <summary> Gets direcly nested elements (non-recursive) </summary>
|
|
||||||
public ObservableCollection<AXmlElement> Elements { |
|
||||||
get { |
|
||||||
if (elements == null) { |
|
||||||
elements = new FilteredCollection<AXmlElement, AXmlObjectCollection<AXmlObject>>(this.Children); |
|
||||||
} |
|
||||||
return elements; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
internal AXmlObject FirstChild { |
|
||||||
get { |
|
||||||
return this.Children[0]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
internal AXmlObject LastChild { |
|
||||||
get { |
|
||||||
return this.Children[this.Children.Count - 1]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override IEnumerable<AXmlObject> GetSelfAndAllChildren() |
|
||||||
{ |
|
||||||
return NRefactory.Utils.TreeTraversal.PreOrder(this, |
|
||||||
delegate(AXmlObject i) { |
|
||||||
AXmlContainer container = i as AXmlContainer; |
|
||||||
if (container != null) |
|
||||||
return container.Children; |
|
||||||
else |
|
||||||
return null; |
|
||||||
} |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a child fully containg the given offset.
|
|
||||||
/// Goes recursively down the tree.
|
|
||||||
/// Specail case if at the end of attribute or text
|
|
||||||
/// </summary>
|
|
||||||
public AXmlObject GetChildAtOffset(int offset) |
|
||||||
{ |
|
||||||
foreach(AXmlObject child in this.Children) { |
|
||||||
if ((child is AXmlAttribute || child is AXmlText) && offset == child.EndOffset) return child; |
|
||||||
if (child.StartOffset < offset && offset < child.EndOffset) { |
|
||||||
AXmlContainer container = child as AXmlContainer; |
|
||||||
if (container != null) { |
|
||||||
return container.GetChildAtOffset(offset); |
|
||||||
} else { |
|
||||||
return child; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return this; // No childs at offset
|
|
||||||
} |
|
||||||
|
|
||||||
// Only these four methods should be used to modify the collection
|
|
||||||
|
|
||||||
/// <summary> To be used exlucively by the parser </summary>
|
|
||||||
internal void AddChild(AXmlObject item) |
|
||||||
{ |
|
||||||
// Childs can be only added to newly parsed items
|
|
||||||
Assert(this.Parent == null, "I have to be new"); |
|
||||||
Assert(item.IsCached, "Added item must be in cache"); |
|
||||||
// Do not set parent pointer
|
|
||||||
this.Children.InsertItemAt(this.Children.Count, item); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> To be used exlucively by the parser </summary>
|
|
||||||
internal void AddChildren(IEnumerable<AXmlObject> items) |
|
||||||
{ |
|
||||||
// Childs can be only added to newly parsed items
|
|
||||||
Assert(this.Parent == null, "I have to be new"); |
|
||||||
// Do not set parent pointer
|
|
||||||
this.Children.InsertItemsAt(this.Children.Count, items.ToList()); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// To be used exclusively by the children update algorithm.
|
|
||||||
/// Insert child and keep links consistent.
|
|
||||||
/// </summary>
|
|
||||||
void InsertChild(int index, AXmlObject item) |
|
||||||
{ |
|
||||||
AXmlParser.Log("Inserting {0} at index {1}", item, index); |
|
||||||
|
|
||||||
Assert(this.Document != null, "Can not insert to dangling object"); |
|
||||||
Assert(item.Parent != this, "Can not own item twice"); |
|
||||||
|
|
||||||
SetParentPointersInTree(item); |
|
||||||
|
|
||||||
this.Children.InsertItemAt(index, item); |
|
||||||
|
|
||||||
this.Document.OnObjectInserted(index, item); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Recursively fix all parent pointer in a tree </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Cache constraint:
|
|
||||||
/// If cached item has parent set, then the whole subtree must be consistent and document set
|
|
||||||
/// </remarks>
|
|
||||||
void SetParentPointersInTree(AXmlObject item) |
|
||||||
{ |
|
||||||
// All items come from the parser cache
|
|
||||||
|
|
||||||
if (item.Parent == null) { |
|
||||||
// Dangling object - either a new parser object or removed tree (still cached)
|
|
||||||
item.Parent = this; |
|
||||||
item.Document = this.Document; |
|
||||||
AXmlContainer container = item as AXmlContainer; |
|
||||||
if (container != null) { |
|
||||||
foreach(AXmlObject child in container.Children) { |
|
||||||
container.SetParentPointersInTree(child); |
|
||||||
} |
|
||||||
} |
|
||||||
} else if (item.Parent == this) { |
|
||||||
// If node is attached and then deattached, it will have null parent pointer
|
|
||||||
// but valid subtree - so its children will alredy have correct parent pointer
|
|
||||||
// like in this case
|
|
||||||
// item.DebugCheckConsistency(false);
|
|
||||||
// Rest of the tree is consistent - do not recurse
|
|
||||||
} else { |
|
||||||
// From cache & parent set => consitent subtree
|
|
||||||
// item.DebugCheckConsistency(false);
|
|
||||||
// The parent (or any futher parents) can not be part of parsed document
|
|
||||||
// becuase otherwise this item would be included twice => safe to change parents
|
|
||||||
// Maintain cache constraint by setting parents to null
|
|
||||||
foreach(AXmlObject ancest in item.GetAncestors().ToList()) { |
|
||||||
ancest.Parent = null; |
|
||||||
} |
|
||||||
item.Parent = this; |
|
||||||
// Rest of the tree is consistent - do not recurse
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// To be used exclusively by the children update algorithm.
|
|
||||||
/// Remove child, set parent to null and notify the document
|
|
||||||
/// </summary>
|
|
||||||
void RemoveChild(int index) |
|
||||||
{ |
|
||||||
AXmlObject removed = this.Children[index]; |
|
||||||
AXmlParser.Log("Removing {0} at index {1}", removed, index); |
|
||||||
|
|
||||||
// Stop tracking if the object can not be used again
|
|
||||||
if (!removed.IsCached) |
|
||||||
this.Document.Parser.TrackedSegments.RemoveParsedObject(removed); |
|
||||||
|
|
||||||
// Null parent pointer
|
|
||||||
Assert(removed.Parent == this, "Inconsistent child"); |
|
||||||
removed.Parent = null; |
|
||||||
|
|
||||||
this.Children.RemoveItemAt(index); |
|
||||||
|
|
||||||
this.Document.OnObjectRemoved(index, removed); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Verify that the subtree is consistent. Only in debug build. </summary>
|
|
||||||
/// <remarks> Parent pointers might be null or pointing somewhere else in parse tree </remarks>
|
|
||||||
internal override void DebugCheckConsistency(bool checkParentPointers) |
|
||||||
{ |
|
||||||
base.DebugCheckConsistency(checkParentPointers); |
|
||||||
AXmlObject prevChild = null; |
|
||||||
int myStartOffset = this.StartOffset; |
|
||||||
int myEndOffset = this.EndOffset; |
|
||||||
foreach(AXmlObject child in this.Children) { |
|
||||||
Assert(child.Length != 0, "Empty child"); |
|
||||||
if (checkParentPointers) { |
|
||||||
Assert(child.Parent != null, "Null parent reference"); |
|
||||||
Assert(child.Parent == this, "Inccorect parent reference"); |
|
||||||
} |
|
||||||
if (this.Document != null) { |
|
||||||
Assert(child.Document != null, "Child has null document"); |
|
||||||
Assert(child.Document == this.Document, "Child is in different document"); |
|
||||||
} |
|
||||||
if (this.IsCached) |
|
||||||
Assert(child.IsCached, "Child not in cache"); |
|
||||||
Assert(myStartOffset <= child.StartOffset && child.EndOffset <= myEndOffset, "Child not within parent text range"); |
|
||||||
if (prevChild != null) |
|
||||||
Assert(prevChild.EndOffset <= child.StartOffset, "Overlaping childs"); |
|
||||||
child.DebugCheckConsistency(checkParentPointers); |
|
||||||
prevChild = child; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <remarks>
|
|
||||||
/// Note the the method is not called recuively.
|
|
||||||
/// Only the helper methods are recursive.
|
|
||||||
/// </remarks>
|
|
||||||
internal void UpdateTreeFrom(AXmlContainer srcContainer) |
|
||||||
{ |
|
||||||
this.StartOffset = srcContainer.StartOffset; // Force the update
|
|
||||||
this.UpdateDataFrom(srcContainer); |
|
||||||
RemoveChildrenNotIn(srcContainer.Children); |
|
||||||
InsertAndUpdateChildrenFrom(srcContainer.Children); |
|
||||||
} |
|
||||||
|
|
||||||
void RemoveChildrenNotIn(IList<AXmlObject> srcList) |
|
||||||
{ |
|
||||||
Dictionary<int, AXmlObject> srcChildren = srcList.ToDictionary(i => i.StartOffset); |
|
||||||
for(int i = 0; i < this.Children.Count;) { |
|
||||||
AXmlObject child = this.Children[i]; |
|
||||||
AXmlObject srcChild; |
|
||||||
|
|
||||||
if (srcChildren.TryGetValue(child.StartOffset, out srcChild) && child.CanUpdateDataFrom(srcChild)) { |
|
||||||
// Keep only one item with given offset (we might have several due to deletion)
|
|
||||||
srcChildren.Remove(child.StartOffset); |
|
||||||
// If contaner that needs updating
|
|
||||||
AXmlContainer childAsContainer = child as AXmlContainer; |
|
||||||
if (childAsContainer != null && child.LastUpdatedFrom != srcChild) |
|
||||||
childAsContainer.RemoveChildrenNotIn(((AXmlContainer)srcChild).Children); |
|
||||||
i++; |
|
||||||
} else { |
|
||||||
RemoveChild(i); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void InsertAndUpdateChildrenFrom(IList<AXmlObject> srcList) |
|
||||||
{ |
|
||||||
for(int i = 0; i < srcList.Count; i++) { |
|
||||||
// End of our list?
|
|
||||||
if (i == this.Children.Count) { |
|
||||||
InsertChild(i, srcList[i]); |
|
||||||
continue; |
|
||||||
} |
|
||||||
AXmlObject child = this.Children[i]; |
|
||||||
AXmlObject srcChild = srcList[i]; |
|
||||||
|
|
||||||
if (child.CanUpdateDataFrom(srcChild)) { // includes offset test
|
|
||||||
// Does it need updating?
|
|
||||||
if (child.LastUpdatedFrom != srcChild) { |
|
||||||
child.UpdateDataFrom(srcChild); |
|
||||||
AXmlContainer childAsContainer = child as AXmlContainer; |
|
||||||
if (childAsContainer != null) |
|
||||||
childAsContainer.InsertAndUpdateChildrenFrom(((AXmlContainer)srcChild).Children); |
|
||||||
} |
|
||||||
} else { |
|
||||||
InsertChild(i, srcChild); |
|
||||||
} |
|
||||||
} |
|
||||||
Assert(this.Children.Count == srcList.Count, "List lengths differ after update"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,69 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// The root object of the XML document
|
|
||||||
/// </summary>
|
|
||||||
public class AXmlDocument: AXmlContainer |
|
||||||
{ |
|
||||||
/// <summary> Parser that produced this document </summary>
|
|
||||||
internal AXmlParser Parser { get; set; } |
|
||||||
|
|
||||||
/// <summary> Occurs when object is added to any part of the document </summary>
|
|
||||||
public event EventHandler<NotifyCollectionChangedEventArgs> ObjectInserted; |
|
||||||
/// <summary> Occurs when object is removed from any part of the document </summary>
|
|
||||||
public event EventHandler<NotifyCollectionChangedEventArgs> ObjectRemoved; |
|
||||||
/// <summary> Occurs before local data of any object in the document changes </summary>
|
|
||||||
public event EventHandler<AXmlObjectEventArgs> ObjectChanging; |
|
||||||
/// <summary> Occurs after local data of any object in the document changed </summary>
|
|
||||||
public event EventHandler<AXmlObjectEventArgs> ObjectChanged; |
|
||||||
|
|
||||||
internal void OnObjectInserted(int index, AXmlObject obj) |
|
||||||
{ |
|
||||||
if (ObjectInserted != null) |
|
||||||
ObjectInserted(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new AXmlObject[] { obj }.ToList(), index)); |
|
||||||
} |
|
||||||
|
|
||||||
internal void OnObjectRemoved(int index, AXmlObject obj) |
|
||||||
{ |
|
||||||
if (ObjectRemoved != null) |
|
||||||
ObjectRemoved(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new AXmlObject[] { obj }.ToList(), index)); |
|
||||||
} |
|
||||||
|
|
||||||
internal void OnObjectChanging(AXmlObject obj) |
|
||||||
{ |
|
||||||
if (ObjectChanging != null) |
|
||||||
ObjectChanging(this, new AXmlObjectEventArgs() { Object = obj } ); |
|
||||||
} |
|
||||||
|
|
||||||
internal void OnObjectChanged(AXmlObject obj) |
|
||||||
{ |
|
||||||
if (ObjectChanged != null) |
|
||||||
ObjectChanged(this, new AXmlObjectEventArgs() { Object = obj } ); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void AcceptVisitor(IAXmlVisitor visitor) |
|
||||||
{ |
|
||||||
visitor.VisitDocument(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
return string.Format(CultureInfo.InvariantCulture, "[{0} Chld:{1}]", base.ToString(), this.Children.Count); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,226 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Logical grouping of other nodes together.
|
|
||||||
/// </summary>
|
|
||||||
public class AXmlElement: AXmlContainer |
|
||||||
{ |
|
||||||
/// <summary> No tags are missing anywhere within this element (recursive) </summary>
|
|
||||||
public bool IsProperlyNested { get; set; } |
|
||||||
/// <returns> True in wellformed XML </returns>
|
|
||||||
public bool HasStartOrEmptyTag { get; set; } |
|
||||||
/// <returns> True in wellformed XML </returns>
|
|
||||||
public bool HasEndTag { get; set; } |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal override bool UpdateDataFrom(AXmlObject source) |
|
||||||
{ |
|
||||||
if (!base.UpdateDataFrom(source)) return false; |
|
||||||
AXmlElement src = (AXmlElement)source; |
|
||||||
// Clear the cache for this - quite expensive
|
|
||||||
attributesAndElements = null; |
|
||||||
if (this.IsProperlyNested != src.IsProperlyNested || |
|
||||||
this.HasStartOrEmptyTag != src.HasStartOrEmptyTag || |
|
||||||
this.HasEndTag != src.HasEndTag) |
|
||||||
{ |
|
||||||
OnChanging(); |
|
||||||
this.IsProperlyNested = src.IsProperlyNested; |
|
||||||
this.HasStartOrEmptyTag = src.HasStartOrEmptyTag; |
|
||||||
this.HasEndTag = src.HasEndTag; |
|
||||||
OnChanged(); |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> The start or empty-element tag if there is any </summary>
|
|
||||||
internal AXmlTag StartTag { |
|
||||||
get { |
|
||||||
Assert(HasStartOrEmptyTag, "Does not have a start tag"); |
|
||||||
return (AXmlTag)this.Children[0]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> The end tag if there is any </summary>
|
|
||||||
internal AXmlTag EndTag { |
|
||||||
get { |
|
||||||
Assert(HasEndTag, "Does not have an end tag"); |
|
||||||
return (AXmlTag)this.Children[this.Children.Count - 1]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
internal override void DebugCheckConsistency(bool checkParentPointers) |
|
||||||
{ |
|
||||||
DebugAssert(Children.Count > 0, "No children"); |
|
||||||
base.DebugCheckConsistency(checkParentPointers); |
|
||||||
} |
|
||||||
|
|
||||||
#region Helpper methods
|
|
||||||
|
|
||||||
/// <summary> Gets attributes of the element </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Warning: this is a cenvenience method to access the attributes of the start tag.
|
|
||||||
/// However, since the start tag might be moved/replaced, this property might return
|
|
||||||
/// different values over time.
|
|
||||||
/// </remarks>
|
|
||||||
public AXmlAttributeCollection Attributes { |
|
||||||
get { |
|
||||||
if (this.HasStartOrEmptyTag) { |
|
||||||
return this.StartTag.Attributes; |
|
||||||
} else { |
|
||||||
return AXmlAttributeCollection.Empty; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ObservableCollection<AXmlObject> attributesAndElements; |
|
||||||
|
|
||||||
/// <summary> Gets both attributes and elements. Expensive, avoid use. </summary>
|
|
||||||
/// <remarks> Warning: the collection will regenerate after each update </remarks>
|
|
||||||
public ObservableCollection<AXmlObject> AttributesAndElements { |
|
||||||
get { |
|
||||||
if (attributesAndElements == null) { |
|
||||||
if (this.HasStartOrEmptyTag) { |
|
||||||
attributesAndElements = new MergedCollection<AXmlObject, ObservableCollection<AXmlObject>> ( |
|
||||||
// New wrapper with RawObject types
|
|
||||||
new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.StartTag.Children, x => x is AXmlAttribute), |
|
||||||
new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.Children, x => x is AXmlElement) |
|
||||||
); |
|
||||||
} else { |
|
||||||
attributesAndElements = new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.Children, x => x is AXmlElement); |
|
||||||
} |
|
||||||
} |
|
||||||
return attributesAndElements; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Name with namespace prefix - exactly as in source </summary>
|
|
||||||
public string Name { |
|
||||||
get { |
|
||||||
if (this.HasStartOrEmptyTag) { |
|
||||||
return this.StartTag.Name; |
|
||||||
} else { |
|
||||||
return this.EndTag.Name; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> The part of name before ":" </summary>
|
|
||||||
/// <returns> Empty string if not found </returns>
|
|
||||||
public string Prefix { |
|
||||||
get { |
|
||||||
return GetNamespacePrefix(this.Name); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> The part of name after ":" </summary>
|
|
||||||
/// <returns> Empty string if not found </returns>
|
|
||||||
public string LocalName { |
|
||||||
get { |
|
||||||
return GetLocalName(this.Name); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Resolved namespace of the name </summary>
|
|
||||||
/// <returns> Empty string if prefix is not found </returns>
|
|
||||||
public string Namespace { |
|
||||||
get { |
|
||||||
string prefix = this.Prefix; |
|
||||||
if (string.IsNullOrEmpty(prefix)) { |
|
||||||
return FindDefaultNamespace(); |
|
||||||
} else { |
|
||||||
return ResolvePrefix(prefix); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Find the defualt namespace for this context </summary>
|
|
||||||
public string FindDefaultNamespace() |
|
||||||
{ |
|
||||||
AXmlElement current = this; |
|
||||||
while(current != null) { |
|
||||||
string namesapce = current.GetAttributeValue(NoNamespace, "xmlns"); |
|
||||||
if (namesapce != null) return namesapce; |
|
||||||
current = current.Parent as AXmlElement; |
|
||||||
} |
|
||||||
return string.Empty; // No namesapce
|
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Recursively resolve given prefix in this context. Prefix must have some value.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns> Empty string if prefix is not found </returns>
|
|
||||||
public string ResolvePrefix(string prefix) |
|
||||||
{ |
|
||||||
if (string.IsNullOrEmpty(prefix)) throw new ArgumentException("No prefix given", "prefix"); |
|
||||||
|
|
||||||
// Implicit namesapces
|
|
||||||
if (prefix == "xml") return XmlNamespace; |
|
||||||
if (prefix == "xmlns") return XmlnsNamespace; |
|
||||||
|
|
||||||
AXmlElement current = this; |
|
||||||
while(current != null) { |
|
||||||
string namesapce = current.GetAttributeValue(XmlnsNamespace, prefix); |
|
||||||
if (namesapce != null) return namesapce; |
|
||||||
current = current.Parent as AXmlElement; |
|
||||||
} |
|
||||||
return NoNamespace; // Can not find prefix
|
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get unquoted value of attribute.
|
|
||||||
/// It looks in the no namespace (empty string).
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Null if not found</returns>
|
|
||||||
public string GetAttributeValue(string localName) |
|
||||||
{ |
|
||||||
return GetAttributeValue(NoNamespace, localName); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get unquoted value of attribute
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="namespace">Namespace. Can be no namepace (empty string), which is the default for attributes.</param>
|
|
||||||
/// <param name="localName">Local name - text after ":"</param>
|
|
||||||
/// <returns>Null if not found</returns>
|
|
||||||
public string GetAttributeValue(string @namespace, string localName) |
|
||||||
{ |
|
||||||
@namespace = @namespace ?? string.Empty; |
|
||||||
foreach(AXmlAttribute attr in this.Attributes.GetByLocalName(localName)) { |
|
||||||
DebugAssert(attr.LocalName == localName, "Bad hashtable"); |
|
||||||
if (attr.Namespace == @namespace) { |
|
||||||
return attr.Value; |
|
||||||
} |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void AcceptVisitor(IAXmlVisitor visitor) |
|
||||||
{ |
|
||||||
visitor.VisitElement(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}' Attr:{2} Chld:{3} Nest:{4}]", base.ToString(), this.Name, this.HasStartOrEmptyTag ? this.StartTag.Children.Count : 0, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,266 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Abstact base class for all types
|
|
||||||
/// </summary>
|
|
||||||
public abstract class AXmlObject: TextSegment |
|
||||||
{ |
|
||||||
/// <summary> Empty string. The namespace used if there is no "xmlns" specified </summary>
|
|
||||||
public static readonly string NoNamespace = string.Empty; |
|
||||||
|
|
||||||
/// <summary> Namespace for "xml:" prefix: "http://www.w3.org/XML/1998/namespace" </summary>
|
|
||||||
public static readonly string XmlNamespace = "http://www.w3.org/XML/1998/namespace"; |
|
||||||
|
|
||||||
/// <summary> Namesapce for "xmlns:" prefix: "http://www.w3.org/2000/xmlns/" </summary>
|
|
||||||
public static readonly string XmlnsNamespace = "http://www.w3.org/2000/xmlns/"; |
|
||||||
|
|
||||||
/// <summary> Parent node. </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// New cached items start with null parent.
|
|
||||||
/// Cache constraint:
|
|
||||||
/// If cached item has parent set, then the whole subtree must be consistent
|
|
||||||
/// </remarks>
|
|
||||||
public AXmlObject Parent { get; set; } |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the document that has owns this object.
|
|
||||||
/// Once set, it is not changed. Not even set to null.
|
|
||||||
/// </summary>
|
|
||||||
internal AXmlDocument Document { get; set; } |
|
||||||
|
|
||||||
/// <summary> Creates new object </summary>
|
|
||||||
protected AXmlObject() |
|
||||||
{ |
|
||||||
this.LastUpdatedFrom = this; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Occurs before the value of any local properties changes. Nested changes do not cause the event to occur </summary>
|
|
||||||
public event EventHandler<AXmlObjectEventArgs> Changing; |
|
||||||
|
|
||||||
/// <summary> Occurs after the value of any local properties changed. Nested changes do not cause the event to occur </summary>
|
|
||||||
public event EventHandler<AXmlObjectEventArgs> Changed; |
|
||||||
|
|
||||||
/// <summary> Raises Changing event </summary>
|
|
||||||
protected void OnChanging() |
|
||||||
{ |
|
||||||
AXmlParser.Log("Changing {0}", this); |
|
||||||
if (Changing != null) { |
|
||||||
Changing(this, new AXmlObjectEventArgs() { Object = this } ); |
|
||||||
} |
|
||||||
AXmlDocument doc = this.Document; |
|
||||||
if (doc != null) { |
|
||||||
doc.OnObjectChanging(this); |
|
||||||
} |
|
||||||
// As a convenience, also rasie an event for the parent element
|
|
||||||
AXmlTag me = this as AXmlTag; |
|
||||||
if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { |
|
||||||
me.Parent.OnChanging(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Raises Changed event </summary>
|
|
||||||
protected void OnChanged() |
|
||||||
{ |
|
||||||
AXmlParser.Log("Changed {0}", this); |
|
||||||
if (Changed != null) { |
|
||||||
Changed(this, new AXmlObjectEventArgs() { Object = this } ); |
|
||||||
} |
|
||||||
AXmlDocument doc = this.Document; |
|
||||||
if (doc != null) { |
|
||||||
doc.OnObjectChanged(this); |
|
||||||
} |
|
||||||
// As a convenience, also rasie an event for the parent element
|
|
||||||
AXmlTag me = this as AXmlTag; |
|
||||||
if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { |
|
||||||
me.Parent.OnChanged(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
List<SyntaxError> syntaxErrors; |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The error that occured in the context of this node (excluding nested nodes)
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<SyntaxError> MySyntaxErrors { |
|
||||||
get { |
|
||||||
if (syntaxErrors == null) { |
|
||||||
return new SyntaxError[] {}; |
|
||||||
} else { |
|
||||||
return syntaxErrors; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The error that occured in the context of this node and all nested nodes.
|
|
||||||
/// It has O(n) cost.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<SyntaxError> SyntaxErrors { |
|
||||||
get { |
|
||||||
return GetSelfAndAllChildren().SelectMany(obj => obj.MySyntaxErrors); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
internal void AddSyntaxError(SyntaxError error) |
|
||||||
{ |
|
||||||
DebugAssert(error.Object == this, "Must own the error"); |
|
||||||
if (this.syntaxErrors == null) this.syntaxErrors = new List<SyntaxError>(); |
|
||||||
syntaxErrors.Add(error); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Throws exception if condition is false </summary>
|
|
||||||
/// <remarks> Present in release mode - use only for very cheap aserts </remarks>
|
|
||||||
protected static void Assert(bool condition, string message) |
|
||||||
{ |
|
||||||
if (!condition) { |
|
||||||
throw new InternalException("Assertion failed: " + message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Throws exception if condition is false </summary>
|
|
||||||
[Conditional("DEBUG")] |
|
||||||
protected static void DebugAssert(bool condition, string message) |
|
||||||
{ |
|
||||||
if (!condition) { |
|
||||||
throw new InternalException("Assertion failed: " + message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Recursively gets self and all nested nodes. </summary>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", |
|
||||||
Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")] |
|
||||||
public virtual IEnumerable<AXmlObject> GetSelfAndAllChildren() |
|
||||||
{ |
|
||||||
return new AXmlObject[] { this }; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Get all ancestors of this node </summary>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", |
|
||||||
Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")] |
|
||||||
public IEnumerable<AXmlObject> GetAncestors() |
|
||||||
{ |
|
||||||
AXmlObject curr = this.Parent; |
|
||||||
while(curr != null) { |
|
||||||
yield return curr; |
|
||||||
curr = curr.Parent; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Call appropriate visit method on the given visitor </summary>
|
|
||||||
public abstract void AcceptVisitor(IAXmlVisitor visitor); |
|
||||||
|
|
||||||
/// <summary> The parser tree object this object was updated from </summary>
|
|
||||||
/// <remarks> Initialized to 'this' </remarks>
|
|
||||||
internal AXmlObject LastUpdatedFrom { get; private set; } |
|
||||||
|
|
||||||
internal bool IsCached { get; set; } |
|
||||||
|
|
||||||
/// <summary> Is call to UpdateDataFrom is allowed? </summary>
|
|
||||||
internal bool CanUpdateDataFrom(AXmlObject source) |
|
||||||
{ |
|
||||||
return |
|
||||||
this.GetType() == source.GetType() && |
|
||||||
this.StartOffset == source.StartOffset && |
|
||||||
(this.LastUpdatedFrom == source || !this.IsCached); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Copy all data from the 'source' to this object </summary>
|
|
||||||
/// <remarks> Returns true if any updates were done </remarks>
|
|
||||||
internal virtual bool UpdateDataFrom(AXmlObject source) |
|
||||||
{ |
|
||||||
Assert(this.GetType() == source.GetType(), "Source has different type"); |
|
||||||
DebugAssert(this.StartOffset == source.StartOffset, "Source has different StartOffset"); |
|
||||||
|
|
||||||
if (this.LastUpdatedFrom == source) { |
|
||||||
DebugAssert(this.EndOffset == source.EndOffset, "Source has different EndOffset"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
Assert(!this.IsCached, "Can not update cached item"); |
|
||||||
Assert(source.IsCached, "Must update from cache"); |
|
||||||
|
|
||||||
this.LastUpdatedFrom = source; |
|
||||||
this.StartOffset = source.StartOffset; |
|
||||||
// In some cases we are just updating objects of that same
|
|
||||||
// type and position and hoping to be luckily right
|
|
||||||
this.EndOffset = source.EndOffset; |
|
||||||
|
|
||||||
// Do not bother comparing - assume changed if non-null
|
|
||||||
if (this.syntaxErrors != null || source.syntaxErrors != null) { |
|
||||||
// May be called again in derived class - oh, well, does not matter
|
|
||||||
OnChanging(); |
|
||||||
this.Document.Parser.TrackedSegments.RemoveSyntaxErrorsOf(this); |
|
||||||
if (source.syntaxErrors == null) { |
|
||||||
this.syntaxErrors = null; |
|
||||||
} else { |
|
||||||
this.syntaxErrors = new List<SyntaxError>(); |
|
||||||
foreach(var error in source.MySyntaxErrors) { |
|
||||||
// The object differs, so create our own copy
|
|
||||||
// The source still might need it in the future and we do not want to break it
|
|
||||||
this.AddSyntaxError(error.Clone(this)); |
|
||||||
} |
|
||||||
} |
|
||||||
this.Document.Parser.TrackedSegments.AddSyntaxErrorsOf(this); |
|
||||||
OnChanged(); |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Verify that the item is consistent. Only in debug build. </summary>
|
|
||||||
[Conditional("DEBUG")] |
|
||||||
internal virtual void DebugCheckConsistency(bool allowNullParent) |
|
||||||
{ |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
return string.Format(CultureInfo.InvariantCulture, "{0}({1}-{2})", this.GetType().Name.Remove(0, 4), this.StartOffset, this.EndOffset); |
|
||||||
} |
|
||||||
|
|
||||||
#region Helpper methods
|
|
||||||
|
|
||||||
/// <summary> The part of name before ":" </summary>
|
|
||||||
/// <returns> Empty string if not found </returns>
|
|
||||||
protected static string GetNamespacePrefix(string name) |
|
||||||
{ |
|
||||||
if (string.IsNullOrEmpty(name)) return string.Empty; |
|
||||||
int colonIndex = name.IndexOf(':'); |
|
||||||
if (colonIndex != -1) { |
|
||||||
return name.Substring(0, colonIndex); |
|
||||||
} else { |
|
||||||
return string.Empty; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> The part of name after ":" </summary>
|
|
||||||
/// <returns> Whole name if ":" not found </returns>
|
|
||||||
protected static string GetLocalName(string name) |
|
||||||
{ |
|
||||||
if (string.IsNullOrEmpty(name)) return string.Empty; |
|
||||||
int colonIndex = name.IndexOf(':'); |
|
||||||
if (colonIndex != -1) { |
|
||||||
return name.Remove(0, colonIndex + 1); |
|
||||||
} else { |
|
||||||
return name ?? string.Empty; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,90 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Collection that is publicly read-only and has support
|
|
||||||
/// for adding/removing multiple items at a time.
|
|
||||||
/// </summary>
|
|
||||||
public class AXmlObjectCollection<T>: Collection<T>, INotifyCollectionChanged |
|
||||||
{ |
|
||||||
/// <summary> Occurs when the collection is changed </summary>
|
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged; |
|
||||||
|
|
||||||
/// <summary> Raises <see cref="CollectionChanged"/> event </summary>
|
|
||||||
// Do not inherit - it is not called if event is null
|
|
||||||
void OnCollectionChanged(NotifyCollectionChangedEventArgs e) |
|
||||||
{ |
|
||||||
if (CollectionChanged != null) { |
|
||||||
CollectionChanged(this, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void ClearItems() |
|
||||||
{ |
|
||||||
throw new NotSupportedException(); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void InsertItem(int index, T item) |
|
||||||
{ |
|
||||||
throw new NotSupportedException(); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void RemoveItem(int index) |
|
||||||
{ |
|
||||||
throw new NotSupportedException(); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void SetItem(int index, T item) |
|
||||||
{ |
|
||||||
throw new NotSupportedException(); |
|
||||||
} |
|
||||||
|
|
||||||
internal void InsertItemAt(int index, T item) |
|
||||||
{ |
|
||||||
base.InsertItem(index, item); |
|
||||||
if (CollectionChanged != null) |
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new T[] { item }.ToList(), index)); |
|
||||||
} |
|
||||||
|
|
||||||
internal void RemoveItemAt(int index) |
|
||||||
{ |
|
||||||
T removed = this[index]; |
|
||||||
base.RemoveItem(index); |
|
||||||
if (CollectionChanged != null) |
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new T[] { removed }.ToList(), index)); |
|
||||||
} |
|
||||||
|
|
||||||
internal void InsertItemsAt(int index, IList<T> items) |
|
||||||
{ |
|
||||||
for(int i = 0; i < items.Count; i++) { |
|
||||||
base.InsertItem(index + i, items[i]); |
|
||||||
} |
|
||||||
if (CollectionChanged != null) |
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)items, index)); |
|
||||||
} |
|
||||||
|
|
||||||
internal void RemoveItemsAt(int index, int count) |
|
||||||
{ |
|
||||||
List<T> removed = new List<T>(); |
|
||||||
for(int i = 0; i < count; i++) { |
|
||||||
removed.Add(this[index]); |
|
||||||
base.RemoveItem(index); |
|
||||||
} |
|
||||||
if (CollectionChanged != null) |
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)removed, index)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,21 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary> Holds event args for event caused by <see cref="AXmlObject"/> </summary>
|
|
||||||
public class AXmlObjectEventArgs: EventArgs |
|
||||||
{ |
|
||||||
/// <summary> The object that caused the event </summary>
|
|
||||||
public AXmlObject Object { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,201 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
using System.Threading; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Creates object tree from XML document.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The created tree fully describes the document and thus the orginal XML file can be
|
|
||||||
/// exactly reproduced.
|
|
||||||
///
|
|
||||||
/// Any further parses will reparse only the changed parts and the existing tree will
|
|
||||||
/// be updated with the changes. The user can add event handlers to be notified of
|
|
||||||
/// the changes. The parser tries to minimize the number of changes to the tree.
|
|
||||||
/// (for example, it will add a single child at the start of collection rather than
|
|
||||||
/// clearing the collection and adding new children)
|
|
||||||
///
|
|
||||||
/// The object tree consists of following types:
|
|
||||||
/// RawObject - Abstact base class for all types
|
|
||||||
/// RawContainer - Abstact base class for all types that can contain child nodes
|
|
||||||
/// RawDocument - The root object of the XML document
|
|
||||||
/// RawElement - Logical grouping of other nodes together. The first child is always the start tag.
|
|
||||||
/// RawTag - Represents any markup starting with "<" and (hopefully) ending with ">"
|
|
||||||
/// RawAttribute - Name-value pair in a tag
|
|
||||||
/// RawText - Whitespace or character data
|
|
||||||
///
|
|
||||||
/// For example, see the following XML and the produced object tree:
|
|
||||||
/// <![CDATA[
|
|
||||||
/// <!-- My favourite quote -->
|
|
||||||
/// <quote author="Albert Einstein">
|
|
||||||
/// Make everything as simple as possible, but not simpler.
|
|
||||||
/// </quote>
|
|
||||||
///
|
|
||||||
/// RawDocument
|
|
||||||
/// RawTag "<!--" "-->"
|
|
||||||
/// RawText " My favourite quote "
|
|
||||||
/// RawElement
|
|
||||||
/// RawTag "<" "quote" ">"
|
|
||||||
/// RawText " "
|
|
||||||
/// RawAttribute 'author="Albert Einstein"'
|
|
||||||
/// RawText "\n Make everything as simple as possible, but not simpler.\n"
|
|
||||||
/// RawTag "</" "quote" ">"
|
|
||||||
/// ]]>
|
|
||||||
///
|
|
||||||
/// The precise content of RawTag depends on what it represents:
|
|
||||||
/// <![CDATA[
|
|
||||||
/// Start tag: "<" Name? (RawText+ RawAttribute)* RawText* (">" | "/>")
|
|
||||||
/// End tag: "</" Name? (RawText+ RawAttribute)* RawText* ">"
|
|
||||||
/// P.instr.: "<?" Name? (RawText)* "?>"
|
|
||||||
/// Comment: "<!--" (RawText)* "-->"
|
|
||||||
/// CData: "<![CDATA[" (RawText)* "]]" ">"
|
|
||||||
/// DTD: "<!DOCTYPE" (RawText+ RawTag)* RawText* ">" (DOCTYPE or other DTD names)
|
|
||||||
/// UknownBang: "<!" (RawText)* ">"
|
|
||||||
/// ]]>
|
|
||||||
///
|
|
||||||
/// The type of tag can be identified by the opening backet.
|
|
||||||
/// There are helpper properties in the RawTag class to identify the type, exactly
|
|
||||||
/// one of the properties will be true.
|
|
||||||
///
|
|
||||||
/// The closing bracket may be missing or may be different for mallformed XML.
|
|
||||||
///
|
|
||||||
/// Note that there can always be multiple consequtive RawText nodes.
|
|
||||||
/// This is to ensure that idividual texts are not too long.
|
|
||||||
///
|
|
||||||
/// XML Spec: http://www.w3.org/TR/xml/
|
|
||||||
/// XML EBNF: http://www.jelks.nu/XML/xmlebnf.html
|
|
||||||
///
|
|
||||||
/// Internals:
|
|
||||||
///
|
|
||||||
/// "Try" methods can silently fail by returning false.
|
|
||||||
/// MoveTo methods do not move if they are already at the given target
|
|
||||||
/// If methods return some object, it must be no-empty. It is up to the caller to ensure
|
|
||||||
/// the context is appropriate for reading.
|
|
||||||
///
|
|
||||||
/// </remarks>
|
|
||||||
public class AXmlParser |
|
||||||
{ |
|
||||||
AXmlDocument userDocument; |
|
||||||
|
|
||||||
internal TrackedSegmentCollection TrackedSegments { get; private set; } |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate syntax error when seeing enity reference other then the build-in ones
|
|
||||||
/// </summary>
|
|
||||||
public bool UnknownEntityReferenceIsError { get; set; } |
|
||||||
|
|
||||||
/// <summary> Create new parser </summary>
|
|
||||||
public AXmlParser() |
|
||||||
{ |
|
||||||
this.Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); |
|
||||||
ClearInternal(); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Throws exception if condition is false </summary>
|
|
||||||
internal static void Assert(bool condition, string message) |
|
||||||
{ |
|
||||||
if (!condition) { |
|
||||||
throw new InternalException("Assertion failed: " + message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Throws exception if condition is false </summary>
|
|
||||||
[Conditional("DEBUG")] |
|
||||||
internal static void DebugAssert(bool condition, string message) |
|
||||||
{ |
|
||||||
if (!condition) { |
|
||||||
throw new InternalException("Assertion failed: " + message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
[Conditional("DEBUG")] |
|
||||||
internal static void Log(string text, params object[] pars) |
|
||||||
{ |
|
||||||
//System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "XML: " + text, pars));
|
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Incrementaly parse the given text.
|
|
||||||
/// You have to hold the write lock.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">
|
|
||||||
/// The full XML text of the new document.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="changesSinceLastParse">
|
|
||||||
/// Changes since last parse. Null will cause full reparse.
|
|
||||||
/// </param>
|
|
||||||
public AXmlDocument Parse(string input, IEnumerable<DocumentChangeEventArgs> changesSinceLastParse) |
|
||||||
{ |
|
||||||
if (!Lock.IsWriteLockHeld) |
|
||||||
throw new InvalidOperationException("Lock needed!"); |
|
||||||
|
|
||||||
// Use changes to invalidate cache
|
|
||||||
if (changesSinceLastParse != null) { |
|
||||||
this.TrackedSegments.UpdateOffsetsAndInvalidate(changesSinceLastParse); |
|
||||||
} else { |
|
||||||
this.TrackedSegments.InvalidateAll(); |
|
||||||
} |
|
||||||
|
|
||||||
TagReader tagReader = new TagReader(this, input); |
|
||||||
List<AXmlObject> tags = tagReader.ReadAllTags(); |
|
||||||
AXmlDocument parsedDocument = new TagMatchingHeuristics(this, input, tags).ReadDocument(); |
|
||||||
tagReader.PrintStringCacheStats(); |
|
||||||
AXmlParser.Log("Updating main DOM tree..."); |
|
||||||
userDocument.UpdateTreeFrom(parsedDocument); |
|
||||||
userDocument.DebugCheckConsistency(true); |
|
||||||
Assert(userDocument.GetSelfAndAllChildren().Count() == parsedDocument.GetSelfAndAllChildren().Count(), "Parsed document and updated document have different number of children"); |
|
||||||
return userDocument; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Makes calls to Parse() thread-safe. Use Lock everywhere Parse() is called.
|
|
||||||
/// </summary>
|
|
||||||
public ReaderWriterLockSlim Lock { get; private set; } |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the last cached version of the document.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="InvalidOperationException">No read lock is held by the current thread.</exception>
|
|
||||||
public AXmlDocument LastDocument { |
|
||||||
get { |
|
||||||
if (!Lock.IsReadLockHeld) |
|
||||||
throw new InvalidOperationException("Read lock needed!"); |
|
||||||
|
|
||||||
return userDocument; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the parser data.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="InvalidOperationException">No write lock is held by the current thread.</exception>
|
|
||||||
public void Clear() |
|
||||||
{ |
|
||||||
if (!Lock.IsWriteLockHeld) |
|
||||||
throw new InvalidOperationException("Write lock needed!"); |
|
||||||
|
|
||||||
ClearInternal(); |
|
||||||
} |
|
||||||
|
|
||||||
void ClearInternal() |
|
||||||
{ |
|
||||||
this.UnknownEntityReferenceIsError = true; |
|
||||||
this.TrackedSegments = new TrackedSegmentCollection(); |
|
||||||
this.userDocument = new AXmlDocument() { Parser = this }; |
|
||||||
this.userDocument.Document = this.userDocument; |
|
||||||
// Track the document
|
|
||||||
this.TrackedSegments.AddParsedObject(this.userDocument, null); |
|
||||||
this.userDocument.IsCached = false; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,108 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Represents any markup starting with "<" and (hopefully) ending with ">"
|
|
||||||
/// </summary>
|
|
||||||
public class AXmlTag: AXmlContainer |
|
||||||
{ |
|
||||||
/// <summary> These identify the start of DTD elements </summary>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification="ReadOnlyCollection is immutable")] |
|
||||||
public static readonly ReadOnlyCollection<string> DtdNames = new ReadOnlyCollection<string>( |
|
||||||
new string[] {"<!DOCTYPE", "<!NOTATION", "<!ELEMENT", "<!ATTLIST", "<!ENTITY" } ); |
|
||||||
|
|
||||||
/// <summary> Opening bracket - usually "<" </summary>
|
|
||||||
public string OpeningBracket { get; internal set; } |
|
||||||
/// <summary> Name following the opening bracket </summary>
|
|
||||||
public string Name { get; internal set; } |
|
||||||
/// <summary> Opening bracket - usually ">" </summary>
|
|
||||||
public string ClosingBracket { get; internal set; } |
|
||||||
|
|
||||||
/// <summary> True if tag starts with "<" </summary>
|
|
||||||
public bool IsStartOrEmptyTag { get { return OpeningBracket == "<"; } } |
|
||||||
/// <summary> True if tag starts with "<" and ends with ">" </summary>
|
|
||||||
public bool IsStartTag { get { return OpeningBracket == "<" && ClosingBracket == ">"; } } |
|
||||||
/// <summary> True if tag starts with "<" and does not end with ">" </summary>
|
|
||||||
public bool IsEmptyTag { get { return OpeningBracket == "<" && ClosingBracket != ">" ; } } |
|
||||||
/// <summary> True if tag starts with "</" </summary>
|
|
||||||
public bool IsEndTag { get { return OpeningBracket == "</"; } } |
|
||||||
/// <summary> True if tag starts with "<?" </summary>
|
|
||||||
public bool IsProcessingInstruction { get { return OpeningBracket == "<?"; } } |
|
||||||
/// <summary> True if tag starts with "<!--" </summary>
|
|
||||||
public bool IsComment { get { return OpeningBracket == "<!--"; } } |
|
||||||
/// <summary> True if tag starts with "<![CDATA[" </summary>
|
|
||||||
public bool IsCData { get { return OpeningBracket == "<![CDATA["; } } |
|
||||||
/// <summary> True if tag starts with one of the DTD starts </summary>
|
|
||||||
public bool IsDocumentType { get { return DtdNames.Contains(OpeningBracket); } } |
|
||||||
/// <summary> True if tag starts with "<!" </summary>
|
|
||||||
public bool IsUnknownBang { get { return OpeningBracket == "<!"; } } |
|
||||||
|
|
||||||
#region Helpper methods
|
|
||||||
|
|
||||||
AXmlAttributeCollection attributes; |
|
||||||
|
|
||||||
/// <summary> Gets attributes of the tag (if applicable) </summary>
|
|
||||||
public AXmlAttributeCollection Attributes { |
|
||||||
get { |
|
||||||
if (attributes == null) { |
|
||||||
attributes = new AXmlAttributeCollection(this.Children); |
|
||||||
} |
|
||||||
return attributes; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
internal override void DebugCheckConsistency(bool checkParentPointers) |
|
||||||
{ |
|
||||||
Assert(OpeningBracket != null, "Null OpeningBracket"); |
|
||||||
Assert(Name != null, "Null Name"); |
|
||||||
Assert(ClosingBracket != null, "Null ClosingBracket"); |
|
||||||
base.DebugCheckConsistency(checkParentPointers); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void AcceptVisitor(IAXmlVisitor visitor) |
|
||||||
{ |
|
||||||
visitor.VisitTag(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal override bool UpdateDataFrom(AXmlObject source) |
|
||||||
{ |
|
||||||
if (!base.UpdateDataFrom(source)) return false; |
|
||||||
AXmlTag src = (AXmlTag)source; |
|
||||||
if (this.OpeningBracket != src.OpeningBracket || |
|
||||||
this.Name != src.Name || |
|
||||||
this.ClosingBracket != src.ClosingBracket) |
|
||||||
{ |
|
||||||
OnChanging(); |
|
||||||
this.OpeningBracket = src.OpeningBracket; |
|
||||||
this.Name = src.Name; |
|
||||||
this.ClosingBracket = src.ClosingBracket; |
|
||||||
OnChanged(); |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}{2}{3}' Attr:{4}]", base.ToString(), this.OpeningBracket, this.Name, this.ClosingBracket, this.Children.Count); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,62 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Whitespace or character data
|
|
||||||
/// </summary>
|
|
||||||
public class AXmlText: AXmlObject |
|
||||||
{ |
|
||||||
/// <summary> The context in which the text occured </summary>
|
|
||||||
internal TextType Type { get; set; } |
|
||||||
/// <summary> The text exactly as in source </summary>
|
|
||||||
public string EscapedValue { get; set; } |
|
||||||
/// <summary> The text with all entity references resloved </summary>
|
|
||||||
public string Value { get; set; } |
|
||||||
/// <summary> True if the text contains only whitespace characters </summary>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", |
|
||||||
Justification = "System.Xml also uses 'Whitespace'")] |
|
||||||
public bool ContainsOnlyWhitespace { get; set; } |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void AcceptVisitor(IAXmlVisitor visitor) |
|
||||||
{ |
|
||||||
visitor.VisitText(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal override bool UpdateDataFrom(AXmlObject source) |
|
||||||
{ |
|
||||||
if (!base.UpdateDataFrom(source)) return false; |
|
||||||
AXmlText src = (AXmlText)source; |
|
||||||
if (this.EscapedValue != src.EscapedValue || |
|
||||||
this.Value != src.Value) |
|
||||||
{ |
|
||||||
OnChanging(); |
|
||||||
this.EscapedValue = src.EscapedValue; |
|
||||||
this.Value = src.Value; |
|
||||||
OnChanged(); |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
return string.Format(CultureInfo.InvariantCulture, "[{0} Text.Length={1}]", base.ToString(), this.EscapedValue.Length); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Text; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Derive from this class to create visitor for the XML tree
|
|
||||||
/// </summary>
|
|
||||||
public abstract class AbstractAXmlVisitor : IAXmlVisitor |
|
||||||
{ |
|
||||||
/// <summary> Visit RawDocument </summary>
|
|
||||||
public virtual void VisitDocument(AXmlDocument document) |
|
||||||
{ |
|
||||||
foreach(AXmlObject child in document.Children) child.AcceptVisitor(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawElement </summary>
|
|
||||||
public virtual void VisitElement(AXmlElement element) |
|
||||||
{ |
|
||||||
foreach(AXmlObject child in element.Children) child.AcceptVisitor(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawTag </summary>
|
|
||||||
public virtual void VisitTag(AXmlTag tag) |
|
||||||
{ |
|
||||||
foreach(AXmlObject child in tag.Children) child.AcceptVisitor(this); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawAttribute </summary>
|
|
||||||
public virtual void VisitAttribute(AXmlAttribute attribute) |
|
||||||
{ |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawText </summary>
|
|
||||||
public virtual void VisitText(AXmlText text) |
|
||||||
{ |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,119 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Linq; |
|
||||||
using System.Text; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Converts the XML tree back to text in canonical form.
|
|
||||||
/// See http://www.w3.org/TR/xml-c14n
|
|
||||||
/// </summary>
|
|
||||||
public class CanonicalPrintAXmlVisitor: AbstractAXmlVisitor |
|
||||||
{ |
|
||||||
StringBuilder sb = new StringBuilder(); |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the pretty printed text
|
|
||||||
/// </summary>
|
|
||||||
public string Output { |
|
||||||
get { |
|
||||||
return sb.ToString(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Create canonical text from a document </summary>
|
|
||||||
public static string Print(AXmlDocument doc) |
|
||||||
{ |
|
||||||
CanonicalPrintAXmlVisitor visitor = new CanonicalPrintAXmlVisitor(); |
|
||||||
visitor.VisitDocument(doc); |
|
||||||
return visitor.Output; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawDocument </summary>
|
|
||||||
public override void VisitDocument(AXmlDocument document) |
|
||||||
{ |
|
||||||
foreach(AXmlObject child in document.Children) { |
|
||||||
AXmlTag childAsTag = child as AXmlTag; |
|
||||||
// Only procssing instructions or elements
|
|
||||||
if (childAsTag != null && childAsTag.IsProcessingInstruction && childAsTag.Name != "xml") { |
|
||||||
VisitTag(childAsTag); |
|
||||||
} else { |
|
||||||
AXmlElement childAsElement = child as AXmlElement; |
|
||||||
if (childAsElement != null) { |
|
||||||
VisitElement(childAsElement); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawElement </summary>
|
|
||||||
public override void VisitElement(AXmlElement element) |
|
||||||
{ |
|
||||||
base.VisitElement(element); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawTag </summary>
|
|
||||||
public override void VisitTag(AXmlTag tag) |
|
||||||
{ |
|
||||||
if (tag.IsStartOrEmptyTag) { |
|
||||||
sb.Append('<'); |
|
||||||
sb.Append(tag.Name); |
|
||||||
foreach(AXmlAttribute attr in tag.Children.OfType<AXmlAttribute>().OrderBy(a => a.Name)) { |
|
||||||
VisitAttribute(attr); |
|
||||||
} |
|
||||||
sb.Append('>'); |
|
||||||
if (tag.IsEmptyTag) { |
|
||||||
// Use explicit start-end pair
|
|
||||||
sb.AppendFormat("</{0}>", tag.Name); |
|
||||||
} |
|
||||||
} else if (tag.IsEndTag) { |
|
||||||
sb.AppendFormat("</{0}>", tag.Name); |
|
||||||
} else if (tag.IsProcessingInstruction) { |
|
||||||
sb.Append("<?"); |
|
||||||
sb.Append(tag.Name); |
|
||||||
foreach(AXmlText text in tag.Children.OfType<AXmlText>()) { |
|
||||||
sb.Append(text.Value); |
|
||||||
} |
|
||||||
if (tag.Children.Count == 0) |
|
||||||
sb.Append(' '); |
|
||||||
sb.Append("?>"); |
|
||||||
} else if (tag.IsCData) { |
|
||||||
foreach(AXmlText text in tag.Children.OfType<AXmlText>()) { |
|
||||||
sb.Append(Escape(text.Value)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawAttribute </summary>
|
|
||||||
public override void VisitAttribute(AXmlAttribute attribute) |
|
||||||
{ |
|
||||||
sb.Append(' '); |
|
||||||
sb.Append(attribute.Name); |
|
||||||
sb.Append("="); |
|
||||||
sb.Append('"'); |
|
||||||
sb.Append(Escape(attribute.Value)); |
|
||||||
sb.Append('"'); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawText </summary>
|
|
||||||
public override void VisitText(AXmlText text) |
|
||||||
{ |
|
||||||
sb.Append(Escape(text.Value)); |
|
||||||
} |
|
||||||
|
|
||||||
static string Escape(string text) |
|
||||||
{ |
|
||||||
return text |
|
||||||
.Replace("&", "&") |
|
||||||
.Replace("<", "<") |
|
||||||
.Replace(">", ">") |
|
||||||
.Replace("\"", """) |
|
||||||
.Replace("\u0009", "	") |
|
||||||
.Replace("\u000A", " ") |
|
||||||
.Replace("\u000D", " "); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,99 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Collection that presents only some items from the wrapped collection.
|
|
||||||
/// It implicitely filters object that are not of type T (or derived).
|
|
||||||
/// </summary>
|
|
||||||
public class FilteredCollection<T, TCollection>: ObservableCollection<T> where TCollection : INotifyCollectionChanged, IList |
|
||||||
{ |
|
||||||
TCollection source; |
|
||||||
Predicate<object> condition; |
|
||||||
List<int> srcPtrs = new List<int>(); // Index to the original collection
|
|
||||||
|
|
||||||
/// <summary> Create unbound collection </summary>
|
|
||||||
protected FilteredCollection() {} |
|
||||||
|
|
||||||
/// <summary> Wrap the given collection. Items of type other then T are filtered </summary>
|
|
||||||
public FilteredCollection(TCollection source) : this (source, x => true) { } |
|
||||||
|
|
||||||
/// <summary> Wrap the given collection. Items of type other then T are filtered. Items not matching the condition are filtered. </summary>
|
|
||||||
public FilteredCollection(TCollection source, Predicate<object> condition) |
|
||||||
{ |
|
||||||
this.source = source; |
|
||||||
this.condition = condition; |
|
||||||
|
|
||||||
this.source.CollectionChanged += SourceCollectionChanged; |
|
||||||
|
|
||||||
Reset(); |
|
||||||
} |
|
||||||
|
|
||||||
void Reset() |
|
||||||
{ |
|
||||||
this.Clear(); |
|
||||||
srcPtrs.Clear(); |
|
||||||
for(int i = 0; i < source.Count; i++) { |
|
||||||
if (source[i] is T && condition(source[i])) { |
|
||||||
this.Add((T)source[i]); |
|
||||||
srcPtrs.Add(i); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
|
||||||
{ |
|
||||||
switch(e.Action) { |
|
||||||
case NotifyCollectionChangedAction.Add: |
|
||||||
// Update pointers
|
|
||||||
for(int i = 0; i < srcPtrs.Count; i++) { |
|
||||||
if (srcPtrs[i] >= e.NewStartingIndex) { |
|
||||||
srcPtrs[i] += e.NewItems.Count; |
|
||||||
} |
|
||||||
} |
|
||||||
// Find where to add items
|
|
||||||
int addIndex = srcPtrs.FindIndex(srcPtr => srcPtr >= e.NewStartingIndex); |
|
||||||
if (addIndex == -1) addIndex = this.Count; |
|
||||||
// Add items to collection
|
|
||||||
for(int i = 0; i < e.NewItems.Count; i++) { |
|
||||||
if (e.NewItems[i] is T && condition(e.NewItems[i])) { |
|
||||||
this.InsertItem(addIndex, (T)e.NewItems[i]); |
|
||||||
srcPtrs.Insert(addIndex, e.NewStartingIndex + i); |
|
||||||
addIndex++; |
|
||||||
} |
|
||||||
} |
|
||||||
break; |
|
||||||
case NotifyCollectionChangedAction.Remove: |
|
||||||
// Remove the item from our collection
|
|
||||||
for(int i = 0; i < e.OldItems.Count; i++) { |
|
||||||
// Anyone points to the removed item?
|
|
||||||
int removeIndex = srcPtrs.IndexOf(e.OldStartingIndex + i); |
|
||||||
// Remove
|
|
||||||
if (removeIndex != -1) { |
|
||||||
this.RemoveAt(removeIndex); |
|
||||||
srcPtrs.RemoveAt(removeIndex); |
|
||||||
} |
|
||||||
} |
|
||||||
// Update pointers
|
|
||||||
for(int i = 0; i < srcPtrs.Count; i++) { |
|
||||||
if (srcPtrs[i] >= e.OldStartingIndex) { |
|
||||||
srcPtrs[i] -= e.OldItems.Count; |
|
||||||
} |
|
||||||
} |
|
||||||
break; |
|
||||||
case NotifyCollectionChangedAction.Reset: |
|
||||||
Reset(); |
|
||||||
break; |
|
||||||
default: |
|
||||||
throw new NotSupportedException(e.Action.ToString()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,29 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Text; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Visitor for the XML tree
|
|
||||||
/// </summary>
|
|
||||||
public interface IAXmlVisitor |
|
||||||
{ |
|
||||||
/// <summary> Visit RawDocument </summary>
|
|
||||||
void VisitDocument(AXmlDocument document); |
|
||||||
|
|
||||||
/// <summary> Visit RawElement </summary>
|
|
||||||
void VisitElement(AXmlElement element); |
|
||||||
|
|
||||||
/// <summary> Visit RawTag </summary>
|
|
||||||
void VisitTag(AXmlTag tag); |
|
||||||
|
|
||||||
/// <summary> Visit RawAttribute </summary>
|
|
||||||
void VisitAttribute(AXmlAttribute attribute); |
|
||||||
|
|
||||||
/// <summary> Visit RawText </summary>
|
|
||||||
void VisitText(AXmlText text); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Runtime.Serialization; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Exception used for internal errors in XML parser.
|
|
||||||
/// This exception indicates a bug in AvalonEdit.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable()] |
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This exception is not public because it is not supposed to be caught by user code - it indicates a bug in AvalonEdit.")] |
|
||||||
class InternalException : Exception |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Creates a new InternalException instance.
|
|
||||||
/// </summary>
|
|
||||||
public InternalException() : base() |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new InternalException instance.
|
|
||||||
/// </summary>
|
|
||||||
public InternalException(string message) : base(message) |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new InternalException instance.
|
|
||||||
/// </summary>
|
|
||||||
public InternalException(string message, Exception innerException) : base(message, innerException) |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new InternalException instance.
|
|
||||||
/// </summary>
|
|
||||||
protected InternalException(SerializationInfo info, StreamingContext context) : base(info, context) |
|
||||||
{ |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,70 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Two collections in sequence
|
|
||||||
/// </summary>
|
|
||||||
public class MergedCollection<T, TCollection> : ObservableCollection<T> where TCollection : INotifyCollectionChanged, IList<T> |
|
||||||
{ |
|
||||||
TCollection a; |
|
||||||
TCollection b; |
|
||||||
|
|
||||||
/// <summary> Create a wrapper containing elements of 'a' and then 'b' </summary>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] |
|
||||||
public MergedCollection(TCollection a, TCollection b) |
|
||||||
{ |
|
||||||
this.a = a; |
|
||||||
this.b = b; |
|
||||||
|
|
||||||
this.a.CollectionChanged += SourceCollectionAChanged; |
|
||||||
this.b.CollectionChanged += SourceCollectionBChanged; |
|
||||||
|
|
||||||
Reset(); |
|
||||||
} |
|
||||||
|
|
||||||
void Reset() |
|
||||||
{ |
|
||||||
this.Clear(); |
|
||||||
foreach(T item in a) this.Add(item); |
|
||||||
foreach(T item in b) this.Add(item); |
|
||||||
} |
|
||||||
|
|
||||||
void SourceCollectionAChanged(object sender, NotifyCollectionChangedEventArgs e) |
|
||||||
{ |
|
||||||
SourceCollectionChanged(0, e); |
|
||||||
} |
|
||||||
|
|
||||||
void SourceCollectionBChanged(object sender, NotifyCollectionChangedEventArgs e) |
|
||||||
{ |
|
||||||
SourceCollectionChanged(a.Count, e); |
|
||||||
} |
|
||||||
|
|
||||||
void SourceCollectionChanged(int collectionStart, NotifyCollectionChangedEventArgs e) |
|
||||||
{ |
|
||||||
switch(e.Action) { |
|
||||||
case NotifyCollectionChangedAction.Add: |
|
||||||
for (int i = 0; i < e.NewItems.Count; i++) { |
|
||||||
this.InsertItem(collectionStart + e.NewStartingIndex + i, (T)e.NewItems[i]); |
|
||||||
} |
|
||||||
break; |
|
||||||
case NotifyCollectionChangedAction.Remove: |
|
||||||
for (int i = 0; i < e.OldItems.Count; i++) { |
|
||||||
this.RemoveAt(collectionStart + e.OldStartingIndex); |
|
||||||
} |
|
||||||
break; |
|
||||||
case NotifyCollectionChangedAction.Reset: |
|
||||||
Reset(); |
|
||||||
break; |
|
||||||
default: |
|
||||||
throw new NotSupportedException(e.Action.ToString()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,69 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Text; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Converts the XML tree back to text.
|
|
||||||
/// The text should exactly match the original.
|
|
||||||
/// </summary>
|
|
||||||
public class PrettyPrintAXmlVisitor: AbstractAXmlVisitor |
|
||||||
{ |
|
||||||
StringBuilder sb = new StringBuilder(); |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the pretty printed text
|
|
||||||
/// </summary>
|
|
||||||
public string Output { |
|
||||||
get { |
|
||||||
return sb.ToString(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Create XML text from a document </summary>
|
|
||||||
public static string PrettyPrint(AXmlDocument doc) |
|
||||||
{ |
|
||||||
PrettyPrintAXmlVisitor visitor = new PrettyPrintAXmlVisitor(); |
|
||||||
visitor.VisitDocument(doc); |
|
||||||
return visitor.Output; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawDocument </summary>
|
|
||||||
public override void VisitDocument(AXmlDocument document) |
|
||||||
{ |
|
||||||
base.VisitDocument(document); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawElement </summary>
|
|
||||||
public override void VisitElement(AXmlElement element) |
|
||||||
{ |
|
||||||
base.VisitElement(element); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawTag </summary>
|
|
||||||
public override void VisitTag(AXmlTag tag) |
|
||||||
{ |
|
||||||
sb.Append(tag.OpeningBracket); |
|
||||||
sb.Append(tag.Name); |
|
||||||
base.VisitTag(tag); |
|
||||||
sb.Append(tag.ClosingBracket); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawAttribute </summary>
|
|
||||||
public override void VisitAttribute(AXmlAttribute attribute) |
|
||||||
{ |
|
||||||
sb.Append(attribute.Name); |
|
||||||
sb.Append(attribute.EqualsSign); |
|
||||||
sb.Append(attribute.QuotedValue); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Visit RawText </summary>
|
|
||||||
public override void VisitText(AXmlText text) |
|
||||||
{ |
|
||||||
sb.Append(text.EscapedValue); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,36 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary> Information about syntax error that occured during parsing </summary>
|
|
||||||
public class SyntaxError: TextSegment |
|
||||||
{ |
|
||||||
/// <summary> Object for which the error occured </summary>
|
|
||||||
public AXmlObject Object { get; internal set; } |
|
||||||
/// <summary> Textual description of the error </summary>
|
|
||||||
public string Message { get; internal set; } |
|
||||||
/// <summary> Any user data </summary>
|
|
||||||
public object Tag { get; set; } |
|
||||||
|
|
||||||
internal SyntaxError Clone(AXmlObject newOwner) |
|
||||||
{ |
|
||||||
return new SyntaxError { |
|
||||||
Object = newOwner, |
|
||||||
Message = Message, |
|
||||||
Tag = Tag, |
|
||||||
StartOffset = StartOffset, |
|
||||||
EndOffset = EndOffset, |
|
||||||
}; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,740 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
using System.Text; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
class TagReader: TokenReader |
|
||||||
{ |
|
||||||
AXmlParser parser; |
|
||||||
TrackedSegmentCollection trackedSegments; |
|
||||||
string input; |
|
||||||
|
|
||||||
public TagReader(AXmlParser parser, string input): base(input) |
|
||||||
{ |
|
||||||
this.parser = parser; |
|
||||||
this.trackedSegments = parser.TrackedSegments; |
|
||||||
this.input = input; |
|
||||||
} |
|
||||||
|
|
||||||
bool TryReadFromCacheOrNew<T>(out T res) where T: AXmlObject, new() |
|
||||||
{ |
|
||||||
return TryReadFromCacheOrNew(out res, t => true); |
|
||||||
} |
|
||||||
|
|
||||||
bool TryReadFromCacheOrNew<T>(out T res, Predicate<T> condition) where T: AXmlObject, new() |
|
||||||
{ |
|
||||||
T cached = trackedSegments.GetCachedObject<T>(this.CurrentLocation, 0, condition); |
|
||||||
if (cached != null) { |
|
||||||
Skip(cached.Length); |
|
||||||
AXmlParser.Assert(cached.Length > 0, "cached elements must not have zero length"); |
|
||||||
res = cached; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
res = new T(); |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void OnParsed(AXmlObject obj) |
|
||||||
{ |
|
||||||
AXmlParser.Log("Parsed {0}", obj); |
|
||||||
trackedSegments.AddParsedObject(obj, this.MaxTouchedLocation > this.CurrentLocation ? (int?)this.MaxTouchedLocation : null); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read all tags in the document in a flat sequence.
|
|
||||||
/// It also includes the text between tags and possibly some properly nested Elements from cache.
|
|
||||||
/// </summary>
|
|
||||||
public List<AXmlObject> ReadAllTags() |
|
||||||
{ |
|
||||||
List<AXmlObject> stream = new List<AXmlObject>(); |
|
||||||
|
|
||||||
while(true) { |
|
||||||
if (IsEndOfFile()) { |
|
||||||
break; |
|
||||||
} else if (TryPeek('<')) { |
|
||||||
AXmlElement elem; |
|
||||||
if (TryReadFromCacheOrNew(out elem, e => e.IsProperlyNested)) { |
|
||||||
stream.Add(elem); |
|
||||||
} else { |
|
||||||
stream.Add(ReadTag()); |
|
||||||
} |
|
||||||
} else { |
|
||||||
stream.AddRange(ReadText(TextType.CharacterData)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return stream; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Context: "<"
|
|
||||||
/// </summary>
|
|
||||||
AXmlTag ReadTag() |
|
||||||
{ |
|
||||||
AssertHasMoreData(); |
|
||||||
|
|
||||||
AXmlTag tag; |
|
||||||
if (TryReadFromCacheOrNew(out tag)) return tag; |
|
||||||
|
|
||||||
tag.StartOffset = this.CurrentLocation; |
|
||||||
|
|
||||||
// Read the opening bracket
|
|
||||||
// It identifies the type of tag and parsing behavior for the rest of it
|
|
||||||
tag.OpeningBracket = ReadOpeningBracket(); |
|
||||||
|
|
||||||
if (tag.IsUnknownBang && !TryPeekWhiteSpace()) |
|
||||||
OnSyntaxError(tag, tag.StartOffset, this.CurrentLocation, "Unknown tag"); |
|
||||||
|
|
||||||
if (tag.IsStartOrEmptyTag || tag.IsEndTag || tag.IsProcessingInstruction) { |
|
||||||
// Read the name
|
|
||||||
string name; |
|
||||||
if (TryReadName(out name)) { |
|
||||||
if (!IsValidName(name)) { |
|
||||||
OnSyntaxError(tag, this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name); |
|
||||||
} |
|
||||||
} else { |
|
||||||
OnSyntaxError(tag, "Element name expected"); |
|
||||||
} |
|
||||||
tag.Name = name; |
|
||||||
} else { |
|
||||||
tag.Name = string.Empty; |
|
||||||
} |
|
||||||
|
|
||||||
bool isXmlDeclr = tag.StartOffset == 0 && tag.Name == "xml"; |
|
||||||
|
|
||||||
if (tag.IsStartOrEmptyTag || tag.IsEndTag || isXmlDeclr) { |
|
||||||
// Read attributes for the tag
|
|
||||||
while(true) { |
|
||||||
// Chech for all forbiden 'name' charcters first - see ReadName
|
|
||||||
if (IsEndOfFile()) break; |
|
||||||
if (TryPeekWhiteSpace()) { |
|
||||||
tag.AddChildren(ReadText(TextType.WhiteSpace)); |
|
||||||
continue; // End of file might be next
|
|
||||||
} |
|
||||||
if (TryPeek('<')) break; |
|
||||||
string endBr; |
|
||||||
int endBrStart = this.CurrentLocation; // Just peek
|
|
||||||
if (TryReadClosingBracket(out endBr)) { // End tag
|
|
||||||
GoBack(endBrStart); |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
// We have "=\'\"" or name - read attribute
|
|
||||||
AXmlAttribute attr = ReadAttribulte(); |
|
||||||
tag.AddChild(attr); |
|
||||||
if (tag.IsEndTag) |
|
||||||
OnSyntaxError(tag, attr.StartOffset, attr.EndOffset, "Attribute not allowed in end tag."); |
|
||||||
} |
|
||||||
} else if (tag.IsDocumentType) { |
|
||||||
tag.AddChildren(ReadContentOfDTD()); |
|
||||||
} else { |
|
||||||
int start = this.CurrentLocation; |
|
||||||
IEnumerable<AXmlObject> text; |
|
||||||
if (tag.IsComment) { |
|
||||||
text = ReadText(TextType.Comment); |
|
||||||
} else if (tag.IsCData) { |
|
||||||
text = ReadText(TextType.CData); |
|
||||||
} else if (tag.IsProcessingInstruction) { |
|
||||||
text = ReadText(TextType.ProcessingInstruction); |
|
||||||
} else if (tag.IsUnknownBang) { |
|
||||||
text = ReadText(TextType.UnknownBang); |
|
||||||
} else { |
|
||||||
throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket)); |
|
||||||
} |
|
||||||
// Enumerate
|
|
||||||
text = text.ToList(); |
|
||||||
// Backtrack at complete start
|
|
||||||
if (IsEndOfFile() || (tag.IsUnknownBang && TryPeek('<'))) { |
|
||||||
GoBack(start); |
|
||||||
} else { |
|
||||||
tag.AddChildren(text); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Read closing bracket
|
|
||||||
string bracket; |
|
||||||
TryReadClosingBracket(out bracket); |
|
||||||
tag.ClosingBracket = bracket; |
|
||||||
|
|
||||||
// Error check
|
|
||||||
int brStart = this.CurrentLocation - (tag.ClosingBracket ?? string.Empty).Length; |
|
||||||
int brEnd = this.CurrentLocation; |
|
||||||
if (tag.Name == null) { |
|
||||||
// One error was reported already
|
|
||||||
} else if (tag.IsStartOrEmptyTag) { |
|
||||||
if (tag.ClosingBracket != ">" && tag.ClosingBracket != "/>") OnSyntaxError(tag, brStart, brEnd, "'>' or '/>' expected"); |
|
||||||
} else if (tag.IsEndTag) { |
|
||||||
if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected"); |
|
||||||
} else if (tag.IsComment) { |
|
||||||
if (tag.ClosingBracket != "-->") OnSyntaxError(tag, brStart, brEnd, "'-->' expected"); |
|
||||||
} else if (tag.IsCData) { |
|
||||||
if (tag.ClosingBracket != "]]>") OnSyntaxError(tag, brStart, brEnd, "']]>' expected"); |
|
||||||
} else if (tag.IsProcessingInstruction) { |
|
||||||
if (tag.ClosingBracket != "?>") OnSyntaxError(tag, brStart, brEnd, "'?>' expected"); |
|
||||||
} else if (tag.IsUnknownBang) { |
|
||||||
if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected"); |
|
||||||
} else if (tag.IsDocumentType) { |
|
||||||
if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected"); |
|
||||||
} else { |
|
||||||
throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket)); |
|
||||||
} |
|
||||||
|
|
||||||
// Attribute name may not apper multiple times
|
|
||||||
var duplicates = tag.Children.OfType<AXmlAttribute>().GroupBy(attr => attr.Name).SelectMany(g => g.Skip(1)); |
|
||||||
foreach(AXmlAttribute attr in duplicates) { |
|
||||||
OnSyntaxError(tag, attr.StartOffset, attr.EndOffset, "Attribute with name '{0}' already exists", attr.Name); |
|
||||||
} |
|
||||||
|
|
||||||
tag.EndOffset = this.CurrentLocation; |
|
||||||
|
|
||||||
OnParsed(tag); |
|
||||||
return tag; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads any of the know opening brackets. (only full bracket)
|
|
||||||
/// Context: "<"
|
|
||||||
/// </summary>
|
|
||||||
string ReadOpeningBracket() |
|
||||||
{ |
|
||||||
// We are using a lot of string literals so that the memory instances are shared
|
|
||||||
//int start = this.CurrentLocation;
|
|
||||||
if (TryRead('<')) { |
|
||||||
if (TryRead('/')) { |
|
||||||
return "</"; |
|
||||||
} else if (TryRead('?')) { |
|
||||||
return "<?"; |
|
||||||
} else if (TryRead('!')) { |
|
||||||
if (TryRead("--")) { |
|
||||||
return "<!--"; |
|
||||||
} else if (TryRead("[CDATA[")) { |
|
||||||
return "<![CDATA["; |
|
||||||
} else { |
|
||||||
foreach(string dtdName in AXmlTag.DtdNames) { |
|
||||||
// the dtdName includes "<!"
|
|
||||||
if (TryRead(dtdName.Remove(0, 2))) return dtdName; |
|
||||||
} |
|
||||||
return "<!"; |
|
||||||
} |
|
||||||
} else { |
|
||||||
return "<"; |
|
||||||
} |
|
||||||
} else { |
|
||||||
throw new InternalException("'<' expected"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads any of the know closing brackets. (only full bracket)
|
|
||||||
/// Context: any
|
|
||||||
/// </summary>
|
|
||||||
bool TryReadClosingBracket(out string bracket) |
|
||||||
{ |
|
||||||
// We are using a lot of string literals so that the memory instances are shared
|
|
||||||
if (TryRead('>')) { |
|
||||||
bracket = ">"; |
|
||||||
} else if (TryRead("/>")) { |
|
||||||
bracket = "/>"; |
|
||||||
} else if (TryRead("?>")) { |
|
||||||
bracket = "?>"; |
|
||||||
} else if (TryRead("-->")) { |
|
||||||
bracket = "-->"; |
|
||||||
} else if (TryRead("]]>")) { |
|
||||||
bracket = "]]>"; |
|
||||||
} else { |
|
||||||
bracket = string.Empty; |
|
||||||
return false; |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
IEnumerable<AXmlObject> ReadContentOfDTD() |
|
||||||
{ |
|
||||||
int start = this.CurrentLocation; |
|
||||||
while(true) { |
|
||||||
if (IsEndOfFile()) break; // End of file
|
|
||||||
TryMoveToNonWhiteSpace(); // Skip whitespace
|
|
||||||
if (TryRead('\'')) TryMoveTo('\''); // Skip single quoted string TODO: Bug
|
|
||||||
if (TryRead('\"')) TryMoveTo('\"'); // Skip single quoted string
|
|
||||||
if (TryRead('[')) { // Start of nested infoset
|
|
||||||
// Reading infoset
|
|
||||||
while(true) { |
|
||||||
if (IsEndOfFile()) break; |
|
||||||
TryMoveToAnyOf('<', ']'); |
|
||||||
if (TryPeek('<')) { |
|
||||||
if (start != this.CurrentLocation) { // Two following tags
|
|
||||||
yield return MakeText(start, this.CurrentLocation); |
|
||||||
} |
|
||||||
yield return ReadTag(); |
|
||||||
start = this.CurrentLocation; |
|
||||||
} |
|
||||||
if (TryPeek(']')) break; |
|
||||||
} |
|
||||||
} |
|
||||||
TryRead(']'); // End of nested infoset
|
|
||||||
if (TryPeek('>')) break; // Proper closing
|
|
||||||
if (TryPeek('<')) break; // Malformed XML
|
|
||||||
TryMoveNext(); // Skip anything else
|
|
||||||
} |
|
||||||
if (start != this.CurrentLocation) { |
|
||||||
yield return MakeText(start, this.CurrentLocation); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Context: name or "=\'\""
|
|
||||||
/// </summary>
|
|
||||||
AXmlAttribute ReadAttribulte() |
|
||||||
{ |
|
||||||
AssertHasMoreData(); |
|
||||||
|
|
||||||
AXmlAttribute attr; |
|
||||||
if (TryReadFromCacheOrNew(out attr)) return attr; |
|
||||||
|
|
||||||
attr.StartOffset = this.CurrentLocation; |
|
||||||
|
|
||||||
// Read name
|
|
||||||
string name; |
|
||||||
if (TryReadName(out name)) { |
|
||||||
if (!IsValidName(name)) { |
|
||||||
OnSyntaxError(attr, this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name); |
|
||||||
} |
|
||||||
} else { |
|
||||||
OnSyntaxError(attr, "Attribute name expected"); |
|
||||||
} |
|
||||||
attr.Name = name; |
|
||||||
|
|
||||||
// Read equals sign and surrounding whitespace
|
|
||||||
int checkpoint = this.CurrentLocation; |
|
||||||
TryMoveToNonWhiteSpace(); |
|
||||||
if (TryRead('=')) { |
|
||||||
int chk2 = this.CurrentLocation; |
|
||||||
TryMoveToNonWhiteSpace(); |
|
||||||
if (!TryPeek('"') && !TryPeek('\'')) { |
|
||||||
// Do not read whitespace if quote does not follow
|
|
||||||
GoBack(chk2); |
|
||||||
} |
|
||||||
attr.EqualsSign = GetText(checkpoint, this.CurrentLocation); |
|
||||||
} else { |
|
||||||
GoBack(checkpoint); |
|
||||||
OnSyntaxError(attr, "'=' expected"); |
|
||||||
attr.EqualsSign = string.Empty; |
|
||||||
} |
|
||||||
|
|
||||||
// Read attribute value
|
|
||||||
int start = this.CurrentLocation; |
|
||||||
char quoteChar = TryPeek('"') ? '"' : '\''; |
|
||||||
bool startsWithQuote; |
|
||||||
if (TryRead(quoteChar)) { |
|
||||||
startsWithQuote = true; |
|
||||||
int valueStart = this.CurrentLocation; |
|
||||||
TryMoveToAnyOf(quoteChar, '<'); |
|
||||||
if (TryRead(quoteChar)) { |
|
||||||
if (!TryPeekAnyOf(' ', '\t', '\n', '\r', '/', '>', '?')) { |
|
||||||
if (TryPeekPrevious('=', 2) || (TryPeekPrevious('=', 3) && TryPeekPrevious(' ', 2))) { |
|
||||||
// This actually most likely means that we are in the next attribute value
|
|
||||||
GoBack(valueStart); |
|
||||||
ReadAttributeValue(quoteChar); |
|
||||||
if (TryRead(quoteChar)) { |
|
||||||
OnSyntaxError(attr, "White space or end of tag expected"); |
|
||||||
} else { |
|
||||||
OnSyntaxError(attr, "Quote {0} expected (or add whitespace after the following one)", quoteChar); |
|
||||||
} |
|
||||||
} else { |
|
||||||
OnSyntaxError(attr, "White space or end of tag expected"); |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
// '<' or end of file
|
|
||||||
GoBack(valueStart); |
|
||||||
ReadAttributeValue(quoteChar); |
|
||||||
OnSyntaxError(attr, "Quote {0} expected", quoteChar); |
|
||||||
} |
|
||||||
} else { |
|
||||||
startsWithQuote = false; |
|
||||||
int valueStart = this.CurrentLocation; |
|
||||||
ReadAttributeValue(null); |
|
||||||
TryRead('\"'); |
|
||||||
TryRead('\''); |
|
||||||
if (valueStart == this.CurrentLocation) { |
|
||||||
OnSyntaxError(attr, "Attribute value expected"); |
|
||||||
} else { |
|
||||||
OnSyntaxError(attr, valueStart, this.CurrentLocation, "Attribute value must be quoted"); |
|
||||||
} |
|
||||||
} |
|
||||||
attr.QuotedValue = GetText(start, this.CurrentLocation); |
|
||||||
attr.Value = Unquote(attr.QuotedValue); |
|
||||||
attr.Value = Dereference(attr, attr.Value, startsWithQuote ? start + 1 : start); |
|
||||||
|
|
||||||
attr.EndOffset = this.CurrentLocation; |
|
||||||
|
|
||||||
OnParsed(attr); |
|
||||||
return attr; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read everything up to quote (excluding), opening/closing tag or attribute signature
|
|
||||||
/// </summary>
|
|
||||||
void ReadAttributeValue(char? quote) |
|
||||||
{ |
|
||||||
while(true) { |
|
||||||
if (IsEndOfFile()) return; |
|
||||||
// What is next?
|
|
||||||
int start = this.CurrentLocation; |
|
||||||
TryMoveToNonWhiteSpace(); // Read white space (if any)
|
|
||||||
if (quote.HasValue) { |
|
||||||
if (TryPeek(quote.Value)) return; |
|
||||||
} else { |
|
||||||
if (TryPeek('"') || TryPeek('\'')) return; |
|
||||||
} |
|
||||||
// Opening/closing tag
|
|
||||||
string endBr; |
|
||||||
if (TryPeek('<') || TryReadClosingBracket(out endBr)) { |
|
||||||
GoBack(start); |
|
||||||
return; |
|
||||||
} |
|
||||||
// Try reading attribute signature
|
|
||||||
string name; |
|
||||||
if (TryReadName(out name)) { |
|
||||||
int nameEnd = this.CurrentLocation; |
|
||||||
if (TryMoveToNonWhiteSpace() && TryRead("=") && |
|
||||||
TryMoveToNonWhiteSpace() && TryPeekAnyOf('"', '\'')) |
|
||||||
{ |
|
||||||
// Start of attribute. Great
|
|
||||||
GoBack(start); |
|
||||||
return; // Done
|
|
||||||
} else { |
|
||||||
// Just some gargabe - make it part of the value
|
|
||||||
GoBack(nameEnd); |
|
||||||
continue; // Read more
|
|
||||||
} |
|
||||||
} |
|
||||||
TryMoveNext(); // Accept everyting else
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
AXmlText MakeText(int start, int end) |
|
||||||
{ |
|
||||||
AXmlParser.DebugAssert(end > start, "Empty text"); |
|
||||||
|
|
||||||
AXmlText text = new AXmlText() { |
|
||||||
StartOffset = start, |
|
||||||
EndOffset = end, |
|
||||||
EscapedValue = GetText(start, end), |
|
||||||
Type = TextType.Other |
|
||||||
}; |
|
||||||
|
|
||||||
OnParsed(text); |
|
||||||
return text; |
|
||||||
} |
|
||||||
|
|
||||||
const int maxEntityLength = 16; // The longest build-in one is 10 ("")
|
|
||||||
const int maxTextFragmentSize = 64; |
|
||||||
const int lookAheadLength = (3 * maxTextFragmentSize) / 2; // More so that we do not get small "what was inserted" fragments
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads text and optionaly separates it into fragments.
|
|
||||||
/// It can also return empty set for no appropriate text input.
|
|
||||||
/// Make sure you enumerate it only once
|
|
||||||
/// </summary>
|
|
||||||
IEnumerable<AXmlObject> ReadText(TextType type) |
|
||||||
{ |
|
||||||
bool lookahead = false; |
|
||||||
while(true) { |
|
||||||
AXmlText text; |
|
||||||
if (TryReadFromCacheOrNew(out text, t => t.Type == type)) { |
|
||||||
// Cached text found
|
|
||||||
yield return text; |
|
||||||
continue; // Read next fragment; the method can handle "no text left"
|
|
||||||
} |
|
||||||
text.Type = type; |
|
||||||
|
|
||||||
// Limit the reading to just a few characters
|
|
||||||
// (the first character not to be read)
|
|
||||||
int fragmentEnd = Math.Min(this.CurrentLocation + maxTextFragmentSize, this.InputLength); |
|
||||||
|
|
||||||
// Look if some futher text has been already processed and align so that
|
|
||||||
// we hit that chache point. It is expensive so it is off for the first run
|
|
||||||
if (lookahead) { |
|
||||||
// Note: Must fit entity
|
|
||||||
AXmlObject nextFragment = trackedSegments.GetCachedObject<AXmlText>(this.CurrentLocation + maxEntityLength, lookAheadLength - maxEntityLength, t => t.Type == type); |
|
||||||
if (nextFragment != null) { |
|
||||||
fragmentEnd = Math.Min(nextFragment.StartOffset, this.InputLength); |
|
||||||
AXmlParser.Log("Parsing only text ({0}-{1}) because later text was already processed", this.CurrentLocation, fragmentEnd); |
|
||||||
} |
|
||||||
} |
|
||||||
lookahead = true; |
|
||||||
|
|
||||||
text.StartOffset = this.CurrentLocation; |
|
||||||
int start = this.CurrentLocation; |
|
||||||
|
|
||||||
// Whitespace would be skipped anyway by any operation
|
|
||||||
TryMoveToNonWhiteSpace(fragmentEnd); |
|
||||||
int wsEnd = this.CurrentLocation; |
|
||||||
|
|
||||||
// Try move to the terminator given by the context
|
|
||||||
if (type == TextType.WhiteSpace) { |
|
||||||
TryMoveToNonWhiteSpace(fragmentEnd); |
|
||||||
} else if (type == TextType.CharacterData) { |
|
||||||
while(true) { |
|
||||||
if (!TryMoveToAnyOf(new char[] {'<', ']'}, fragmentEnd)) break; // End of fragment
|
|
||||||
if (TryPeek('<')) break; |
|
||||||
if (TryPeek(']')) { |
|
||||||
if (TryPeek("]]>")) { |
|
||||||
OnSyntaxError(text, this.CurrentLocation, this.CurrentLocation + 3, "']]>' is not allowed in text"); |
|
||||||
} |
|
||||||
TryMoveNext(); |
|
||||||
continue; |
|
||||||
} |
|
||||||
throw new Exception("Infinite loop"); |
|
||||||
} |
|
||||||
} else if (type == TextType.Comment) { |
|
||||||
// Do not report too many errors
|
|
||||||
bool errorReported = false; |
|
||||||
while(true) { |
|
||||||
if (!TryMoveTo('-', fragmentEnd)) break; // End of fragment
|
|
||||||
if (TryPeek("-->")) break; |
|
||||||
if (TryPeek("--") && !errorReported) { |
|
||||||
OnSyntaxError(text, this.CurrentLocation, this.CurrentLocation + 2, "'--' is not allowed in comment"); |
|
||||||
errorReported = true; |
|
||||||
} |
|
||||||
TryMoveNext(); |
|
||||||
} |
|
||||||
} else if (type == TextType.CData) { |
|
||||||
while(true) { |
|
||||||
// We can not use use TryMoveTo("]]>", fragmentEnd) because it may incorectly accept "]" at the end of fragment
|
|
||||||
if (!TryMoveTo(']', fragmentEnd)) break; // End of fragment
|
|
||||||
if (TryPeek("]]>")) break; |
|
||||||
TryMoveNext(); |
|
||||||
} |
|
||||||
} else if (type == TextType.ProcessingInstruction) { |
|
||||||
while(true) { |
|
||||||
if (!TryMoveTo('?', fragmentEnd)) break; // End of fragment
|
|
||||||
if (TryPeek("?>")) break; |
|
||||||
TryMoveNext(); |
|
||||||
} |
|
||||||
} else if (type == TextType.UnknownBang) { |
|
||||||
TryMoveToAnyOf(new char[] {'<', '>'}, fragmentEnd); |
|
||||||
} else { |
|
||||||
throw new Exception("Uknown type " + type); |
|
||||||
} |
|
||||||
|
|
||||||
text.ContainsOnlyWhitespace = (wsEnd == this.CurrentLocation); |
|
||||||
|
|
||||||
// Terminal found or real end was reached;
|
|
||||||
bool finished = this.CurrentLocation < fragmentEnd || IsEndOfFile(); |
|
||||||
|
|
||||||
if (!finished) { |
|
||||||
// We have to continue reading more text fragments
|
|
||||||
|
|
||||||
// If there is entity reference, make sure the next segment starts with it to prevent framentation
|
|
||||||
int entitySearchStart = Math.Max(start + 1 /* data for us */, this.CurrentLocation - maxEntityLength); |
|
||||||
int entitySearchLength = this.CurrentLocation - entitySearchStart; |
|
||||||
if (entitySearchLength > 0) { |
|
||||||
// Note that LastIndexOf works backward
|
|
||||||
int entityIndex = input.LastIndexOf('&', this.CurrentLocation - 1, entitySearchLength); |
|
||||||
if (entityIndex != -1) { |
|
||||||
GoBack(entityIndex); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
text.EscapedValue = GetText(start, this.CurrentLocation); |
|
||||||
if (type == TextType.CharacterData) { |
|
||||||
// Normalize end of line first
|
|
||||||
text.Value = Dereference(text, NormalizeEndOfLine(text.EscapedValue), start); |
|
||||||
} else { |
|
||||||
text.Value = text.EscapedValue; |
|
||||||
} |
|
||||||
text.EndOffset = this.CurrentLocation; |
|
||||||
|
|
||||||
if (text.EscapedValue.Length > 0) { |
|
||||||
OnParsed(text); |
|
||||||
yield return text; |
|
||||||
} |
|
||||||
|
|
||||||
if (finished) { |
|
||||||
yield break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#region Helper methods
|
|
||||||
|
|
||||||
void OnSyntaxError(AXmlObject obj, string message, params object[] args) |
|
||||||
{ |
|
||||||
OnSyntaxError(obj, this.CurrentLocation, this.CurrentLocation + 1, message, args); |
|
||||||
} |
|
||||||
|
|
||||||
public static void OnSyntaxError(AXmlObject obj, int start, int end, string message, params object[] args) |
|
||||||
{ |
|
||||||
if (end <= start) end = start + 1; |
|
||||||
string formattedMessage = string.Format(CultureInfo.InvariantCulture, message, args); |
|
||||||
AXmlParser.Log("Syntax error ({0}-{1}): {2}", start, end, formattedMessage); |
|
||||||
obj.AddSyntaxError(new SyntaxError() { |
|
||||||
Object = obj, |
|
||||||
StartOffset = start, |
|
||||||
EndOffset = end, |
|
||||||
Message = formattedMessage, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
static bool IsValidName(string name) |
|
||||||
{ |
|
||||||
try { |
|
||||||
System.Xml.XmlConvert.VerifyName(name); |
|
||||||
return true; |
|
||||||
} catch (System.Xml.XmlException) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Remove quoting from the given string </summary>
|
|
||||||
static string Unquote(string quoted) |
|
||||||
{ |
|
||||||
if (string.IsNullOrEmpty(quoted)) return string.Empty; |
|
||||||
char first = quoted[0]; |
|
||||||
if (quoted.Length == 1) return (first == '"' || first == '\'') ? string.Empty : quoted; |
|
||||||
char last = quoted[quoted.Length - 1]; |
|
||||||
if (first == '"' || first == '\'') { |
|
||||||
if (first == last) { |
|
||||||
// Remove both quotes
|
|
||||||
return quoted.Substring(1, quoted.Length - 2); |
|
||||||
} else { |
|
||||||
// Remove first quote
|
|
||||||
return quoted.Remove(0, 1); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (last == '"' || last == '\'') { |
|
||||||
// Remove last quote
|
|
||||||
return quoted.Substring(0, quoted.Length - 1); |
|
||||||
} else { |
|
||||||
// Keep whole string
|
|
||||||
return quoted; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static string NormalizeEndOfLine(string text) |
|
||||||
{ |
|
||||||
return text.Replace("\r\n", "\n").Replace("\r", "\n"); |
|
||||||
} |
|
||||||
|
|
||||||
string Dereference(AXmlObject owner, string text, int textLocation) |
|
||||||
{ |
|
||||||
StringBuilder sb = null; // The dereferenced text so far (all up to 'curr')
|
|
||||||
int curr = 0; |
|
||||||
while(true) { |
|
||||||
// Reached end of input
|
|
||||||
if (curr == text.Length) { |
|
||||||
if (sb != null) { |
|
||||||
return sb.ToString(); |
|
||||||
} else { |
|
||||||
return text; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Try to find reference
|
|
||||||
int start = text.IndexOf('&', curr); |
|
||||||
|
|
||||||
// No more references found
|
|
||||||
if (start == -1) { |
|
||||||
if (sb != null) { |
|
||||||
sb.Append(text, curr, text.Length - curr); // Add rest
|
|
||||||
return sb.ToString(); |
|
||||||
} else { |
|
||||||
return text; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Append text before the enitiy reference
|
|
||||||
if (sb == null) sb = new StringBuilder(text.Length); |
|
||||||
sb.Append(text, curr, start - curr); |
|
||||||
curr = start; |
|
||||||
|
|
||||||
// Process the entity
|
|
||||||
int errorLoc = textLocation + sb.Length; |
|
||||||
|
|
||||||
// Find entity name
|
|
||||||
int end = text.IndexOfAny(new char[] {'&', ';'}, start + 1, Math.Min(maxEntityLength, text.Length - (start + 1))); |
|
||||||
if (end == -1 || text[end] == '&') { |
|
||||||
// Not found
|
|
||||||
OnSyntaxError(owner, errorLoc, errorLoc + 1, "Entity reference must be terminated with ';'"); |
|
||||||
// Keep '&'
|
|
||||||
sb.Append('&'); |
|
||||||
curr++; |
|
||||||
continue; // Restart and next character location
|
|
||||||
} |
|
||||||
string name = text.Substring(start + 1, end - (start + 1)); |
|
||||||
|
|
||||||
// Resolve the name
|
|
||||||
string replacement; |
|
||||||
if (name.Length == 0) { |
|
||||||
replacement = null; |
|
||||||
OnSyntaxError(owner, errorLoc + 1, errorLoc + 1, "Entity name expected"); |
|
||||||
} else if (name == "amp") { |
|
||||||
replacement = "&"; |
|
||||||
} else if (name == "lt") { |
|
||||||
replacement = "<"; |
|
||||||
} else if (name == "gt") { |
|
||||||
replacement = ">"; |
|
||||||
} else if (name == "apos") { |
|
||||||
replacement = "'"; |
|
||||||
} else if (name == "quot") { |
|
||||||
replacement = "\""; |
|
||||||
} else if (name.Length > 0 && name[0] == '#') { |
|
||||||
int num; |
|
||||||
if (name.Length > 1 && name[1] == 'x') { |
|
||||||
if (!int.TryParse(name.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out num)) { |
|
||||||
num = -1; |
|
||||||
OnSyntaxError(owner, errorLoc + 3, errorLoc + 1 + name.Length, "Hexadecimal code of unicode character expected"); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (!int.TryParse(name.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out num)) { |
|
||||||
num = -1; |
|
||||||
OnSyntaxError(owner, errorLoc + 2, errorLoc + 1 + name.Length, "Numeric code of unicode character expected"); |
|
||||||
} |
|
||||||
} |
|
||||||
if (num != -1) { |
|
||||||
try { |
|
||||||
replacement = char.ConvertFromUtf32(num); |
|
||||||
} catch (ArgumentOutOfRangeException) { |
|
||||||
replacement = null; |
|
||||||
OnSyntaxError(owner, errorLoc + 2, errorLoc + 1 + name.Length, "Invalid unicode character U+{0:X} ({0})", num); |
|
||||||
} |
|
||||||
} else { |
|
||||||
replacement = null; |
|
||||||
} |
|
||||||
} else if (!IsValidName(name)) { |
|
||||||
replacement = null; |
|
||||||
OnSyntaxError(owner, errorLoc + 1, errorLoc + 1, "Invalid entity name"); |
|
||||||
} else { |
|
||||||
replacement = null; |
|
||||||
if (parser.UnknownEntityReferenceIsError) { |
|
||||||
OnSyntaxError(owner, errorLoc, errorLoc + 1 + name.Length + 1, "Unknown entity reference '{0}'", name); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Append the replacement to output
|
|
||||||
if (replacement != null) { |
|
||||||
sb.Append(replacement); |
|
||||||
} else { |
|
||||||
sb.Append('&'); |
|
||||||
sb.Append(name); |
|
||||||
sb.Append(';'); |
|
||||||
} |
|
||||||
curr = end + 1; |
|
||||||
continue; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,39 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
using System.Collections.Specialized; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary> Identifies the context in which the text occured </summary>
|
|
||||||
enum TextType |
|
||||||
{ |
|
||||||
/// <summary> Ends with non-whitespace </summary>
|
|
||||||
WhiteSpace, |
|
||||||
|
|
||||||
/// <summary> Ends with "<"; "]]>" is error </summary>
|
|
||||||
CharacterData, |
|
||||||
|
|
||||||
/// <summary> Ends with "-->"; "--" is error </summary>
|
|
||||||
Comment, |
|
||||||
|
|
||||||
/// <summary> Ends with "]]>" </summary>
|
|
||||||
CData, |
|
||||||
|
|
||||||
/// <summary> Ends with "?>" </summary>
|
|
||||||
ProcessingInstruction, |
|
||||||
|
|
||||||
/// <summary> Ends with "<" or ">" </summary>
|
|
||||||
UnknownBang, |
|
||||||
|
|
||||||
/// <summary> Unknown </summary>
|
|
||||||
Other |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,309 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
class TokenReader |
|
||||||
{ |
|
||||||
string input; |
|
||||||
int inputLength; |
|
||||||
int currentLocation; |
|
||||||
|
|
||||||
// CurrentLocation is assumed to be touched and the fact does not
|
|
||||||
// have to be recorded in this variable.
|
|
||||||
// This stores any value bigger than that if applicable.
|
|
||||||
// Acutal value is max(currentLocation, maxTouchedLocation).
|
|
||||||
int maxTouchedLocation; |
|
||||||
|
|
||||||
public int InputLength { |
|
||||||
get { return inputLength; } |
|
||||||
} |
|
||||||
|
|
||||||
public int CurrentLocation { |
|
||||||
get { return currentLocation; } |
|
||||||
} |
|
||||||
|
|
||||||
public int MaxTouchedLocation { |
|
||||||
get { return Math.Max(currentLocation, maxTouchedLocation); } |
|
||||||
} |
|
||||||
|
|
||||||
public TokenReader(string input) |
|
||||||
{ |
|
||||||
this.input = input; |
|
||||||
this.inputLength = input.Length; |
|
||||||
} |
|
||||||
|
|
||||||
protected bool IsEndOfFile() |
|
||||||
{ |
|
||||||
return currentLocation == inputLength; |
|
||||||
} |
|
||||||
|
|
||||||
protected bool HasMoreData() |
|
||||||
{ |
|
||||||
return currentLocation < inputLength; |
|
||||||
} |
|
||||||
|
|
||||||
protected void AssertHasMoreData() |
|
||||||
{ |
|
||||||
AXmlParser.Assert(HasMoreData(), "Unexpected end of file"); |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveNext() |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
|
|
||||||
currentLocation++; |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
protected void Skip(int count) |
|
||||||
{ |
|
||||||
AXmlParser.Assert(currentLocation + count <= inputLength, "Skipping after the end of file"); |
|
||||||
currentLocation += count; |
|
||||||
} |
|
||||||
|
|
||||||
protected void GoBack(int oldLocation) |
|
||||||
{ |
|
||||||
AXmlParser.Assert(oldLocation <= currentLocation, "Trying to move forward"); |
|
||||||
maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation); |
|
||||||
currentLocation = oldLocation; |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryRead(char c) |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
|
|
||||||
if (input[currentLocation] == c) { |
|
||||||
currentLocation++; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryReadAnyOf(params char[] c) |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
|
|
||||||
if (c.Contains(input[currentLocation])) { |
|
||||||
currentLocation++; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryRead(string text) |
|
||||||
{ |
|
||||||
if (TryPeek(text)) { |
|
||||||
currentLocation += text.Length; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryPeekPrevious(char c, int back) |
|
||||||
{ |
|
||||||
if (currentLocation - back == inputLength) return false; |
|
||||||
if (currentLocation - back < 0 ) return false; |
|
||||||
|
|
||||||
return input[currentLocation - back] == c; |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryPeek(char c) |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
|
|
||||||
return input[currentLocation] == c; |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryPeekAnyOf(params char[] chars) |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
|
|
||||||
return chars.Contains(input[currentLocation]); |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryPeek(string text) |
|
||||||
{ |
|
||||||
if (!TryPeek(text[0])) return false; // Early exit
|
|
||||||
|
|
||||||
maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation + (text.Length - 1)); |
|
||||||
// The following comparison 'touches' the end of file - it does depend on the end being there
|
|
||||||
if (currentLocation + text.Length > inputLength) return false; |
|
||||||
|
|
||||||
return input.Substring(currentLocation, text.Length) == text; |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryPeekWhiteSpace() |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
|
|
||||||
char c = input[currentLocation]; |
|
||||||
return ((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r'); |
|
||||||
} |
|
||||||
|
|
||||||
// The move functions do not have to move if already at target
|
|
||||||
// The move functions allow 'overriding' of the document length
|
|
||||||
|
|
||||||
protected bool TryMoveTo(char c) |
|
||||||
{ |
|
||||||
return TryMoveTo(c, inputLength); |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveTo(char c, int inputLength) |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
int index = input.IndexOf(c, currentLocation, inputLength - currentLocation); |
|
||||||
if (index != -1) { |
|
||||||
currentLocation = index; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
currentLocation = inputLength; |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveToAnyOf(params char[] c) |
|
||||||
{ |
|
||||||
return TryMoveToAnyOf(c, inputLength); |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveToAnyOf(char[] c, int inputLength) |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
int index = input.IndexOfAny(c, currentLocation, inputLength - currentLocation); |
|
||||||
if (index != -1) { |
|
||||||
currentLocation = index; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
currentLocation = inputLength; |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveTo(string text) |
|
||||||
{ |
|
||||||
return TryMoveTo(text, inputLength); |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveTo(string text, int inputLength) |
|
||||||
{ |
|
||||||
if (currentLocation == inputLength) return false; |
|
||||||
int index = input.IndexOf(text, currentLocation, inputLength - currentLocation, StringComparison.Ordinal); |
|
||||||
if (index != -1) { |
|
||||||
maxTouchedLocation = index + text.Length - 1; |
|
||||||
currentLocation = index; |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
currentLocation = inputLength; |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveToNonWhiteSpace() |
|
||||||
{ |
|
||||||
return TryMoveToNonWhiteSpace(inputLength); |
|
||||||
} |
|
||||||
|
|
||||||
protected bool TryMoveToNonWhiteSpace(int inputLength) |
|
||||||
{ |
|
||||||
while(true) { |
|
||||||
if (currentLocation == inputLength) return false; // Reject end of file
|
|
||||||
char c = input[currentLocation]; |
|
||||||
if (((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) { |
|
||||||
currentLocation++; // Accept white-space
|
|
||||||
continue; |
|
||||||
} else { |
|
||||||
return true; // Found non-white-space
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read a name token.
|
|
||||||
/// The following characters are not allowed:
|
|
||||||
/// "" End of file
|
|
||||||
/// " \n\r\t" Whitesapce
|
|
||||||
/// "=\'\"" Attribute value
|
|
||||||
/// "<>/?" Tags
|
|
||||||
/// </summary>
|
|
||||||
/// <returns> True if read at least one character </returns>
|
|
||||||
protected bool TryReadName(out string res) |
|
||||||
{ |
|
||||||
int start = currentLocation; |
|
||||||
// Keep reading up to invalid character
|
|
||||||
while(true) { |
|
||||||
if (currentLocation == inputLength) break; // Reject end of file
|
|
||||||
char c = input[currentLocation]; |
|
||||||
if (0x41 <= (int)c) { // Accpet from 'A' onwards
|
|
||||||
currentLocation++; |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || // Reject whitesapce
|
|
||||||
c == '=' || c == '\'' || c == '"' || // Reject attributes
|
|
||||||
c == '<' || c == '>' || c == '/' || c == '?') { // Reject tags
|
|
||||||
break; |
|
||||||
} else { |
|
||||||
currentLocation++; |
|
||||||
continue; // Accept other character
|
|
||||||
} |
|
||||||
} |
|
||||||
if (start == currentLocation) { |
|
||||||
res = string.Empty; |
|
||||||
return false; |
|
||||||
} else { |
|
||||||
res = GetText(start, currentLocation); |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected string GetText(int start, int end) |
|
||||||
{ |
|
||||||
AXmlParser.Assert(end <= currentLocation, "Reading ahead of current location"); |
|
||||||
if (start == inputLength && end == inputLength) { |
|
||||||
return string.Empty; |
|
||||||
} else { |
|
||||||
return GetCachedString(input.Substring(start, end - start)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Dictionary<string, string> stringCache = new Dictionary<string, string>(); |
|
||||||
int stringCacheRequestedCount; |
|
||||||
int stringCacheRequestedSize; |
|
||||||
int stringCacheStoredCount; |
|
||||||
int stringCacheStoredSize; |
|
||||||
|
|
||||||
string GetCachedString(string cached) |
|
||||||
{ |
|
||||||
stringCacheRequestedCount += 1; |
|
||||||
stringCacheRequestedSize += 8 + 2 * cached.Length; |
|
||||||
// Do not bother with long strings
|
|
||||||
if (cached.Length > 32) { |
|
||||||
stringCacheStoredCount += 1; |
|
||||||
stringCacheStoredSize += 8 + 2 * cached.Length; |
|
||||||
return cached; |
|
||||||
} |
|
||||||
if (stringCache.ContainsKey(cached)) { |
|
||||||
// Get the instance from the cache instead
|
|
||||||
return stringCache[cached]; |
|
||||||
} else { |
|
||||||
// Add to cache
|
|
||||||
stringCacheStoredCount += 1; |
|
||||||
stringCacheStoredSize += 8 + 2 * cached.Length; |
|
||||||
stringCache.Add(cached, cached); |
|
||||||
return cached; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void PrintStringCacheStats() |
|
||||||
{ |
|
||||||
AXmlParser.Log("String cache: Requested {0} ({1} bytes); Actaully stored {2} ({3} bytes); {4}% stored", stringCacheRequestedCount, stringCacheRequestedSize, stringCacheStoredCount, stringCacheStoredSize, stringCacheRequestedSize == 0 ? 0 : stringCacheStoredSize * 100 / stringCacheRequestedSize); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,165 +0,0 @@ |
|||||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|
||||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
||||||
|
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Globalization; |
|
||||||
using System.Linq; |
|
||||||
|
|
||||||
using ICSharpCode.AvalonEdit.Document; |
|
||||||
|
|
||||||
namespace ICSharpCode.AvalonEdit.Xml |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Holds all objects that need to keep offsets up to date.
|
|
||||||
/// </summary>
|
|
||||||
class TrackedSegmentCollection |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Holds all types of objects in one collection.
|
|
||||||
/// </summary>
|
|
||||||
TextSegmentCollection<TextSegment> segments = new TextSegmentCollection<TextSegment>(); |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is used to identify what memory range was touched by object
|
|
||||||
/// The default is (StartOffset, EndOffset + 1) which is not stored
|
|
||||||
/// </summary>
|
|
||||||
class TouchedRange: TextSegment |
|
||||||
{ |
|
||||||
public AXmlObject TouchedByObject { get; set; } |
|
||||||
} |
|
||||||
|
|
||||||
public void UpdateOffsetsAndInvalidate(IEnumerable<DocumentChangeEventArgs> changes) |
|
||||||
{ |
|
||||||
foreach(DocumentChangeEventArgs change in changes) { |
|
||||||
// Update offsets of all items
|
|
||||||
segments.UpdateOffsets(change); |
|
||||||
|
|
||||||
// Remove any items affected by the change
|
|
||||||
AXmlParser.Log("Changed {0}-{1}", change.Offset, change.Offset + change.InsertionLength); |
|
||||||
// Removing will cause one of the ends to be set to change.Offset
|
|
||||||
// FindSegmentsContaining includes any segments touching
|
|
||||||
// so that conviniently takes care of the +1 byte
|
|
||||||
var segmentsContainingOffset = segments.FindOverlappingSegments(change.Offset, change.InsertionLength); |
|
||||||
foreach(AXmlObject obj in segmentsContainingOffset.OfType<AXmlObject>().Where(o => o.IsCached)) { |
|
||||||
InvalidateCache(obj, false); |
|
||||||
} |
|
||||||
foreach(TouchedRange range in segmentsContainingOffset.OfType<TouchedRange>()) { |
|
||||||
AXmlParser.Log("Found that {0} dependeds on ({1}-{2})", range.TouchedByObject, range.StartOffset, range.EndOffset); |
|
||||||
InvalidateCache(range.TouchedByObject, true); |
|
||||||
segments.Remove(range); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invlidates all objects. That is, the whole document has changed.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks> We still have to keep the items becuase they might be in the document </remarks>
|
|
||||||
public void InvalidateAll() |
|
||||||
{ |
|
||||||
AXmlParser.Log("Invalidating all objects"); |
|
||||||
foreach(AXmlObject obj in segments.OfType<AXmlObject>()) { |
|
||||||
obj.IsCached = false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Add object to cache, optionally adding extra memory tracking </summary>
|
|
||||||
public void AddParsedObject(AXmlObject obj, int? maxTouchedLocation) |
|
||||||
{ |
|
||||||
if (!(obj.Length > 0 || obj is AXmlDocument)) |
|
||||||
AXmlParser.Assert(false, string.Format(CultureInfo.InvariantCulture, "Invalid object {0}. It has zero length.", obj)); |
|
||||||
// // Expensive check
|
|
||||||
// if (obj is AXmlContainer) {
|
|
||||||
// int objStartOffset = obj.StartOffset;
|
|
||||||
// int objEndOffset = obj.EndOffset;
|
|
||||||
// foreach(AXmlObject child in ((AXmlContainer)obj).Children) {
|
|
||||||
// AXmlParser.Assert(objStartOffset <= child.StartOffset && child.EndOffset <= objEndOffset, "Wrong nesting");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
segments.Add(obj); |
|
||||||
AddSyntaxErrorsOf(obj); |
|
||||||
obj.IsCached = true; |
|
||||||
if (maxTouchedLocation != null) { |
|
||||||
// location is assumed to be read so the range ends at (location + 1)
|
|
||||||
// For example eg for "a_" it is (0-2)
|
|
||||||
TouchedRange range = new TouchedRange() { |
|
||||||
StartOffset = obj.StartOffset, |
|
||||||
EndOffset = maxTouchedLocation.Value + 1, |
|
||||||
TouchedByObject = obj |
|
||||||
}; |
|
||||||
segments.Add(range); |
|
||||||
AXmlParser.Log("{0} touched range ({1}-{2})", obj, range.StartOffset, range.EndOffset); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Removes object with all of its non-cached children </summary>
|
|
||||||
public void RemoveParsedObject(AXmlObject obj) |
|
||||||
{ |
|
||||||
// Cached objects may be used in the future - do not remove them
|
|
||||||
if (obj.IsCached) return; |
|
||||||
segments.Remove(obj); |
|
||||||
RemoveSyntaxErrorsOf(obj); |
|
||||||
AXmlParser.Log("Stopped tracking {0}", obj); |
|
||||||
|
|
||||||
AXmlContainer container = obj as AXmlContainer; |
|
||||||
if (container != null) { |
|
||||||
foreach (AXmlObject child in container.Children) { |
|
||||||
RemoveParsedObject(child); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void AddSyntaxErrorsOf(AXmlObject obj) |
|
||||||
{ |
|
||||||
foreach(SyntaxError syntaxError in obj.MySyntaxErrors) { |
|
||||||
segments.Add(syntaxError); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void RemoveSyntaxErrorsOf(AXmlObject obj) |
|
||||||
{ |
|
||||||
foreach(SyntaxError syntaxError in obj.MySyntaxErrors) { |
|
||||||
segments.Remove(syntaxError); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
IEnumerable<AXmlObject> FindParents(AXmlObject child) |
|
||||||
{ |
|
||||||
int childStartOffset = child.StartOffset; |
|
||||||
int childEndOffset = child.EndOffset; |
|
||||||
foreach(AXmlObject parent in segments.FindSegmentsContaining(child.StartOffset).OfType<AXmlObject>()) { |
|
||||||
// Parent is anyone wholy containg the child
|
|
||||||
if (parent.StartOffset <= childStartOffset && childEndOffset <= parent.EndOffset && parent != child) { |
|
||||||
yield return parent; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Invalidates items, but keeps tracking them </summary>
|
|
||||||
/// <remarks> Can be called redundantly (from range tacking) </remarks>
|
|
||||||
void InvalidateCache(AXmlObject obj, bool includeParents) |
|
||||||
{ |
|
||||||
if (includeParents) { |
|
||||||
foreach(AXmlObject parent in FindParents(obj)) { |
|
||||||
parent.IsCached = false; |
|
||||||
AXmlParser.Log("Invalidating cached item {0} (it is parent)", parent); |
|
||||||
} |
|
||||||
} |
|
||||||
obj.IsCached = false; |
|
||||||
AXmlParser.Log("Invalidating cached item {0}", obj); |
|
||||||
} |
|
||||||
|
|
||||||
public T GetCachedObject<T>(int offset, int lookaheadCount, Predicate<T> conditon) where T: AXmlObject, new() |
|
||||||
{ |
|
||||||
TextSegment obj = segments.FindFirstSegmentWithStartAfter(offset); |
|
||||||
while(obj != null && offset <= obj.StartOffset && obj.StartOffset <= offset + lookaheadCount) { |
|
||||||
if (obj is T && ((AXmlObject)obj).IsCached && conditon((T)obj)) { |
|
||||||
return (T)obj; |
|
||||||
} |
|
||||||
obj = segments.GetNextSegment(obj); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue