//
// Copyright (C) 2010 Novell Inc. http://novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xaml.Schema;
using PairSystem.Collections.Generic.KeyValuePairSystem.Xaml.XamlMember;
namespace System.Xaml{
// FIXME: is GetObject supported by this reader?
public class XamlXmlReader : XamlReader, IXamlLineInfo
{
#region constructors
public XamlXmlReader (Stream stream)
: this (stream, (XamlXmlReaderSettings) null)
{
}
public XamlXmlReader (string fileName)
: this (fileName, (XamlXmlReaderSettings) null)
{
}
public XamlXmlReader (TextReader textReader)
: this (textReader, (XamlXmlReaderSettings) null)
{
}
public XamlXmlReader (XmlReader xmlReader)
: this (xmlReader, (XamlXmlReaderSettings) null)
{
}
public XamlXmlReader (Stream stream, XamlSchemaContext schemaContext)
: this (stream, schemaContext, null)
{
}
public XamlXmlReader (Stream stream, XamlXmlReaderSettings settings)
: this (stream, new XamlSchemaContext (null, null), settings)
{
}
public XamlXmlReader (string fileName, XamlSchemaContext schemaContext)
: this (fileName, schemaContext, null)
{
}
public XamlXmlReader (string fileName, XamlXmlReaderSettings settings)
: this (fileName, new XamlSchemaContext (null, null), settings)
{
}
public XamlXmlReader (TextReader textReader, XamlSchemaContext schemaContext)
: this (textReader, schemaContext, null)
{
}
public XamlXmlReader (TextReader textReader, XamlXmlReaderSettings settings)
: this (textReader, new XamlSchemaContext (null, null), settings)
{
}
public XamlXmlReader (XmlReader xmlReader, XamlSchemaContext schemaContext)
: this (xmlReader, schemaContext, null)
{
}
public XamlXmlReader (XmlReader xmlReader, XamlXmlReaderSettings settings)
: this (xmlReader, new XamlSchemaContext (null, null), settings)
{
}
public XamlXmlReader (Stream stream, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings)
: this (XmlReader.Create (stream), schemaContext, settings)
{
}
static readonly XmlReaderSettings file_reader_settings = new XmlReaderSettings () { CloseInput =true };
public XamlXmlReader (string fileName, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings)
: this (XmlReader.Create (fileName, file_reader_settings), schemaContext, settings)
{
}
public XamlXmlReader (TextReader textReader, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings)
: this (XmlReader.Create (textReader), schemaContext, settings)
{
}
public XamlXmlReader (XmlReader xmlReader, XamlSchemaContext schemaContext, XamlXmlReaderSettings settings)
{
if (xmlReader == null)
throw new ArgumentNullException ("xmlReader");
if (schemaContext == null)
throw new ArgumentNullException ("schemaContext");
sctx = schemaContext;
this.settings = settings ?? new XamlXmlReaderSettings ();
// filter out some nodes.
var xrs = new XmlReaderSettings () {
CloseInput = this.settings.CloseInput,
IgnoreComments = true,
IgnoreProcessingInstructions = true,
IgnoreWhitespace = true };
r = XmlReader.Create (xmlReader, xrs);
line_info = r as IXmlLineInfo;
xaml_namespace_resolver = new NamespaceResolver (r as IXmlNamespaceResolver);
}
#endregion
XmlReader r;
IXmlLineInfo line_info;
XamlSchemaContext sctx;
XamlXmlReaderSettings settings;
bool is_eof;
XamlNodeType node_type;
object current;
bool inside_object_not_member, is_empty_object, is_empty_member;
Stack<XamlType> types = new Stack<XamlType> ();
XamlMember current_member;
List<Pair> stored_members = new List<Pair> ();
IEnumerator<Pair> stored_member_enumerator;
IXamlNamespaceResolver xaml_namespace_resolver;
public bool HasLineInfo {
get { return line_info != null && line_info.HasLineInfo (); }
}
public override bool IsEof {
get { return is_eof; }
}
public int LineNumber {
get { return line_info != null ? line_info.LineNumber : 0; }
}
public int LinePosition {
get { return line_info != null ? line_info.LinePosition : 0; }
}
public override XamlMember Member {
get { return current as XamlMember; }
}
public override NamespaceDeclaration Namespace {
get { return current as NamespaceDeclaration; }
}
public override XamlNodeType NodeType {
get { return node_type; }
}
public override XamlSchemaContext SchemaContext {
get { return sctx; }
}
public override XamlType Type {
get { return current as XamlType; }
}
public override object Value {
get { return NodeType == XamlNodeType.Value ? current : null; }
}
public override bool Read ()
{
if (IsDisposed)
throw new ObjectDisposedException ("reader");
if (is_eof)
return false;
// check this before is_empty_* so that they aren't ignored.
if (MoveToNextAttributeMember ())
return true;
if (is_empty_object) {
is_empty_object = false;
ReadEndType ();
return true;
}
if (is_empty_member) {
is_empty_member = false;
ReadEndMember ();
return true;
}
bool attrIterated = false;
if (r.NodeType == XmlNodeType.Attribute) {
attrIterated = true;
if (r.MoveToNextAttribute ())
if (CheckNextNamespace ())
return true;
}
if (!r.EOF)
r.MoveToContent ();
if (r.EOF) {
is_eof = true;
return false;
}
switch (r.NodeType) {
case XmlNodeType.Element:
// could be: StartObject, StartMember, optionally preceding NamespaceDeclarations
if (!attrIterated && r.MoveToFirstAttribute ())
if (CheckNextNamespace ())
return true;
r.MoveToElement ();
if (inside_object_not_member)
ReadStartMember ();
else
ReadStartType ();
return true;
case XmlNodeType.EndElement:
// could be: EndObject, EndMember
if (inside_object_not_member)
ReadEndType ();
else
ReadEndMember ();
return true;
default:
// could be: Value
ReadValue ();
return true;
}
}
bool CheckNextNamespace ()
{
do {
if (r.NamespaceURI == XamlLanguage.Xmlns2000Namespace) {
current = new NamespaceDeclaration (r.Value, r.Prefix == "xmlns" ? r.LocalName : String.Empty);
node_type = XamlNodeType.NamespaceDeclaration;
return true;
}
} while (r.MoveToNextAttribute ());
return false;
}
void ReadStartType ()
{
string name = r.LocalName;
string ns = r.NamespaceURI;
string typeArgNames = null;
var atts = ProcessAttributes ();
if (!r.IsEmptyElement) {
r.Read ();
do {
r.MoveToContent ();
switch (r.NodeType) {
case XmlNodeType.Element:
// FIXME: parse type arguments etc.
case XmlNodeType.EndElement:
break;
default:
// this value is for Initialization
// FIXME: this could also be a WrappedContents
stored_members.Add (new Pair (XamlLanguage.Initialization, r.Value));
r.Read ();
continue;
}
break;
} while (true);
}
else
is_empty_object = true;
// check TypeArguments to resolve Type, and remove them from the list. They don't appear as a node.
foreach (var p in stored_members) {
if (p.Key == XamlLanguage.TypeArguments) {
typeArgNames = p.Value;
stored_members.Remove (p);
break;
}
}
XamlType xt;
IList<XamlTypeName> typeArgs = typeArgNames == null ? null : XamlTypeName.ParseList (typeArgNames, xaml_namespace_resolver);
Type rtype = XamlLanguage.ResolveXamlTypeName (ns, name, typeArgs, xaml_namespace_resolver);
if (rtype == null)
throw new XamlParseException (String.Format ("Cannot resolve runtime namespace from XML namespace '{0}', local name '{1}' and type argument '{2}'", ns, name, typeArgNames));
xt = sctx.GetXamlType (rtype);
if (xt == null)
// FIXME: .NET just treats the node as empty!
// we have to sort out what to do here.
throw new XamlParseException (String.Format ("Failed to create a XAML type for '{0}' in namespace '{1}'", name, ns));
types.Push (xt);
current = xt;
foreach (var p in atts) {
var xm = xt.GetMember (p.Key);
if (xm != null)
stored_members.Add (new Pair (xm, p.Value));
// ignore unknown attribute
}
node_type = XamlNodeType.StartObject;
inside_object_not_member = true;
// The next Read() results are likely directives.
stored_member_enumerator = stored_members.GetEnumerator ();
}
void ReadStartMember ()
{
var name = r.LocalName;
current_member = types.Peek ().GetMember (name);
current = current_member;
node_type = XamlNodeType.StartMember;
inside_object_not_member = false;
}
void ReadEndType ()
{
r.Read ();
types.Pop ();
current = null;
node_type = XamlNodeType.EndObject;
inside_object_not_member = false;
}
void ReadEndMember ()
{
r.Read ();
current = current_member = null;
node_type = XamlNodeType.EndMember;
inside_object_not_member = true;
}
void ReadValue ()
{
// FIXME: (probably) use ValueSerializer to deserialize the value to the expected type.
current = r.Value;
r.Read ();
node_type = XamlNodeType.Value;
}
// returns remaining attributes to be processed
Dictionary<string,string> ProcessAttributes ()
{
var l = stored_members;
// base
string xmlbase = r.GetAttribute ("base", XamlLanguage.Xml1998Namespace) ?? r.BaseURI;
if (types.Count == 0 && xmlbase != null) // top
l.Add (new Pair (XamlLanguage.Base, xmlbase));
var atts = new Dictionary<string,string> ();
if (r.MoveToFirstAttribute ()) {
do {
if (r.NamespaceURI == XamlLanguage.Xmlns2000Namespace)
continue;
XamlDirective d = XamlLanguage.AllDirectives.FirstOrDefault (dd => (dd.AllowedLocation & AllowedMemberLocations.Attribute) != 0 && dd.Name == r.LocalName);
if (d != null) {
l.Add (new Pair (d, r.Value));
continue;
}
if (r.NamespaceURI == String.Empty) {
atts.Add (r.LocalName, r.Value);
continue;
}
// Should we just ignore unknown attribute in XAML namespace or any other namespaces ?
// Probably yes for compatibility with future version.
} while (r.MoveToNextAttribute ());
r.MoveToElement ();
}
return atts;
}
bool MoveToNextAttributeMember ()
{
if (stored_member_enumerator != null) {
// FIXME: value might have to be deserialized.
switch (node_type) {
case XamlNodeType.StartObject:
case XamlNodeType.EndMember:
// -> StartMember
if (stored_member_enumerator.MoveNext ()) {
current = current_member = stored_member_enumerator.Current.Key;
node_type = XamlNodeType.StartMember;
return true;
}
break;
case XamlNodeType.StartMember:
// -> Value
current = stored_member_enumerator.Current.Value;
node_type = XamlNodeType.Value;
return true;
case XamlNodeType.Value:
// -> EndMember
current = null;
node_type = XamlNodeType.EndMember;
return true;
}
}
stored_members.Clear ();
stored_member_enumerator = null;
return false;
}
string GetLineString ()
{
return HasLineInfo ? String.Format (" Line {0}, at {1}", LineNumber, LinePosition) : String.Empty;
}
class NamespaceResolver : IXamlNamespaceResolver
{
IXmlNamespaceResolver source;
public NamespaceResolver (IXmlNamespaceResolver source)
{
this.source = source;
}
public string GetNamespace (string prefix)
{
return source.LookupNamespace (prefix);
}
public IEnumerable<NamespaceDeclaration> GetNamespacePrefixes ()
{
foreach (var p in source.GetNamespacesInScope (XmlNamespaceScope.All))
yield return new NamespaceDeclaration (p.Value, p.Key);
}
}
}
}
|