public void TestHtmlExample()
        {
            var schema = new Schema(TestHelper.GetSimpleHTMLSchema());

            var document = TestHelper.StreamParseString(@"
				(html [
					(head)
					(body [
						(p ""string"") 
						(img {src ""file.png""}) 
						(img {src ""file2.png"" title ""file 2""}) 
						(p ""other string"") 
						(img {title ""other order"" src ""file3.png""})
					])
				])
			"            , schema);

            Assert.IsTrue(schema.Validate(document));
        }
        public void TestExceptions()
        {
            const string NOT_HERE = "Should've thrown an Exception instead of ending up here.";

            var d = TestHelper.ParseString("(node {a 0} [1 2])");

            try {
                new Schema(d);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema must be a (schema) node.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"(schema {top-element (literal-element {type ""number""})} (invalid-node-type))"));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Invalid schema type description.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"(schema {top-element (literal-element {type ""type""})})"));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Literal element references an undeclared type 'ud:type'", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""type""})}
							(node-type {name ""type""}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Literal element references non-literal type.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (node-element {name ""n"" type ""type""})})
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Node element references an undeclared type 'ud:type'", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (node-element {name ""n"" type ""type""})}
							(literal-type {name ""type"" base-type ""number""}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Node element references non-node type.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"(schema {top-element 1})"));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema element description must be a node.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"(schema {top-element (list [1 2])})"));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema list description must have exactly one element description.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"(schema {top-element (unknown)})"));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Invalid schema element description.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"(schema {top-element (literal-element {type ""type""})} 1)"));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema type description must be a node.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""type""})}
							(literal-type {name ""type"" conditions 1}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema condition description must be a node.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""type""})}
							(literal-type {name ""type"" conditions (condition [1 2])}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema condition description must have exactly one value.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""type""})}
							(literal-type {name ""type"" conditions (condition 1)}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema condition description must be a string.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""type""})}
							(literal-type {name ""type"" conditions (one-of-conditions)}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema one-of-conditions description must have at least one value.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""type""})}
							(literal-type {name ""type"" conditions (all-of-conditions 1)}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("All of schema all-of-conditions description values must be nodes.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""type""})}
							(literal-type {name ""type"" conditions (c)}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Invalid schema condition description.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""number""})}
							(node-type {name ""type""} [
								(not-attribute)
							]))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema attribute description must be an (attribute) node.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""number""})}
							(node-type {name ""type""} [
								(attribute [1 2])
							]))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Schema attribute description must have exactly one element description.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""number""})}
							(node-type {name ""type""} [
								(attribute (literal-element {type ""number""}))
							]))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Attribute 'name' expected, but not found.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""number""})}
							(node-type {name ""type""} [
								(attribute {name true} (literal-element {type ""number""}))
							]))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Attribute 'name' expected to be a string.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""number""})}
							(node-type {name ""type""} [
								(attribute {name ""n""} (literal-element {type ""number""}))
							]))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Attribute 'required' expected, but not found.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""number""})}
							(node-type {name ""type""} [
								(attribute {name ""n"" required 1} (literal-element {type ""number""}))
							]))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Attribute 'required' expected to be a boolean value.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (list {min true} (literal-element {type ""number""}))})
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Attribute 'min' expected to be a number.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (list {min 1.1} (literal-element {type ""number""}))})
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Attribute 'min' expected to be an integer.", e.Message);
            }

            // schema type exceptions

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""ud""})}
							(literal-type {name ""ud"" base-type ""unknown""}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Unknown built-in type 'unknown' used in literal-type description.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""ud""})}
							(literal-type {name ""ud"" base-type ""unknown"" conditions 
								(condition ""=5]/[=1"")}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Invalid condition '=5]/[=1'.", e.Message);
            }

            try {
                new Schema(TestHelper.ParseString(@"
					(schema 
							{top-element (literal-element {type ""ud""})}
							(literal-type {name ""ud"" base-type ""unknown"" conditions 
								(condition ""]"")}))
				"                ));
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Invalid condition ']'.", e.Message);
            }

            // validate partial

            var schema = new Schema(TestHelper.GetSimpleHTMLSchema());

            try {
                TestHelper.StreamParseString(@"
					(html [
						(head)
						(body [
							(p ""string"") 
							(img {src ""file.png""}) 
							(img {src ""file2.png"" title ""file 2""})
							(not-one-of-permitted)
							(p ""other string"") 
							(img {title ""other order"" src ""file3.png""})
						])
					])
				"                , schema);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Document already does not match the schema:\nElement \'/html/body#1/not-one-of-permitted#3\' does not match neither of allowed options even partially:\n\tElement \'/html/body#1/not-one-of-permitted#3\' must be a (p) node.\n\tElement \'/html/body#1/not-one-of-permitted#3\' must be a (img) node.\n", e.Message);
            }

            try {
                TestHelper.StreamParseString(@"
					(html [
						(head)
					])
				"                , schema);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nA sequence of 2 elements expected, 1 element(s) found.", e.Message);
            }

            try {
                TestHelper.StreamParseString(@"
					(html [
						(head)
						(body)
						(f**k)
					])
				"                , schema);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Document already does not match the schema:\nA sequence of 2 elements expected, more (3) elements found.", e.Message);
            }

            const string EXAMPLE     = "(n {a 5})";
            var          SDF_EXAMPLE = TestHelper.ParseString(EXAMPLE);

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type""} [
							(attribute {name ""a"" required true} (list {min 2} (literal-element {type ""number""})))
						])
					])
				"                ));
                Assert.IsTrue(!listSchema.Validate(SDF_EXAMPLE));

                TestHelper.StreamParseString(EXAMPLE, listSchema);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nLess than minimal (2) amount of elements in a list.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type""} [
							(attribute {name ""a"" required true} (list {max 0} (literal-element {type ""number""})))
						])
					])
				"                ));
                Assert.IsTrue(!listSchema.Validate(SDF_EXAMPLE));

                TestHelper.StreamParseString(EXAMPLE, listSchema);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Document already does not match the schema:\nMore than maximum (0) amount of elements in a list.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type""} [
							(attribute {name ""a"" required true} (sequence [
								(literal-element {type ""number""})
								(literal-element {type ""string""})
							]))
						])
					])
				"                ));
                Assert.IsTrue(!listSchema.Validate(SDF_EXAMPLE));

                TestHelper.StreamParseString(EXAMPLE, listSchema);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nA sequence of 2 elements expected, one element found.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children (one-of [
								(literal-element {type ""null""})
								(literal-element {type ""string""})
							])
						})
					])
				"                ));
                var ex         = "(n [null 5 null])";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));

                TestHelper.StreamParseString(ex, listSchema);
                Assert.Fail(NOT_HERE);
            } catch (InvalidDataException e) {
                Assert.AreEqual("Document already does not match the schema:\nElement does not match neither of allowed options even partially.", e.Message);
            }

            {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children (one-of [
								(literal-element {type ""nll""})
								(literal-element {type ""str""})
								(literal-element {type ""bln""})
							])
						})

						(literal-type {name ""nll"" base-type ""null""})
						(literal-type {name ""str"" base-type ""string""})
						(literal-type {name ""bln"" base-type ""bool"" conditions (condition ""=true"")})
					])
				"                ));
                var ex         = "(n null)";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(listSchema.Validate(sdf));

                TestHelper.AssertAreDeeplyEqual(sdf, TestHelper.StreamParseString(ex, listSchema));

                Assert.IsTrue(!listSchema.Validate(TestHelper.ParseString("(n 5)")));
                Assert.IsTrue(!listSchema.Validate(TestHelper.ParseString("(n false)")));
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children 
							(node-element {name ""nd"" type ""nd""})
						})

						(node-type {name ""nd"" conditions (condition ""=true"")})
					])
				"                ));
                var ex         = "(n (nd))";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));

                TestHelper.AssertAreDeeplyEqual(sdf, TestHelper.StreamParseString(ex, listSchema));
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nElement \'/n/nd\' does not match \'=true\' condition.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children 
							(node-element {name ""nd"" type ""nd""})
						})

						(node-type {name ""nd"" conditions (condition ""=true"") children (literal-element {type ""number""})})
					])
				"                ));
                var ex         = "(n (nd))";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));

                TestHelper.AssertAreDeeplyEqual(sdf, TestHelper.StreamParseString(ex, listSchema));
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nOne literal expected, multiple (or none) found.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children 
							(node-element {name ""nd"" type ""nd""})
						})

						(node-type {name ""nd"" conditions (condition ""=true"")})
					])
				"                ));
                var ex         = "(n [5 6])";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));

                TestHelper.AssertAreDeeplyEqual(sdf, TestHelper.StreamParseString(ex, listSchema));
            } catch (InvalidDataException e) {
                Assert.AreEqual("Document already does not match the schema:\nOne node expected, multiple found.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children 
							(node-element {name ""nd"" type ""nd""})
						})

						(node-type {name ""nd""} [(attribute {name ""n"" required true} (literal-element {type ""number""}))])
					])
				"                ));
                var ex         = "(n (nd))";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));

                TestHelper.AssertAreDeeplyEqual(sdf, TestHelper.StreamParseString(ex, listSchema));
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nRequired attribute \'n\' is missing on element \'/n/nd\'.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children 
							(node-element {name ""nd"" type ""nd""})
						})

						(node-type {name ""nd"" conditions (all-of-conditions [(condition ""=true"") (condition ""=false"")])})
					])
				"                ));
                var ex         = "(n (nd))";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));

                TestHelper.AssertAreDeeplyEqual(sdf, TestHelper.StreamParseString(ex, listSchema));
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nOne of the conditions is not met:\n\tElement \'/n/nd\' does not match \'=true\' condition.", e.Message);
            }

            try {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children 
							(node-element {name ""nd"" type ""nd""})
						})

						(node-type {name ""nd"" conditions (one-of-conditions [(condition ""=true"") (condition ""=false"")])})
					])
				"                ));
                var ex         = "(n (nd))";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));

                TestHelper.AssertAreDeeplyEqual(sdf, TestHelper.StreamParseString(ex, listSchema));
            } catch (InvalidDataException e) {
                Assert.AreEqual("File is read completely, but document does not match the schema:\nNone of the following multiple conditions is not met:\n\tElement \'/n/nd\' does not match \'=true\' condition.\n\tElement \'/n/nd\' does not match \'=false\' condition.\n", e.Message);
            }

            {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type""} [
							(attribute {name ""a"" required true} (sequence [
								(literal-element {type ""number""})
							]))
						])
					])
				"                ));
                var ex         = "(n {a null})";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));
            }

            {
                var listSchema = new Schema(TestHelper.StreamParseString(@"
					(schema {top-element (node-element {name ""n"" type ""n-type""})} [
						(node-type {name ""n-type"" children (sequence [
								(literal-element {type ""number""})
							])
						})
					])
				"                ));
                var ex         = "(n [null])";
                var sdf        = TestHelper.ParseString(ex);
                Assert.IsTrue(!listSchema.Validate(sdf));
            }
        }