diff --git a/README.md b/README.md
index e051d86..e72acc1 100644
--- a/README.md
+++ b/README.md
@@ -315,3 +315,17 @@ save_file(xdoc, filename) # save xdoc to an XML file
string(xdoc) # formatted XML doc to a string
show(io, xdoc) # output formatted XML document
```
+
+##### Functions to validate a document
+
+```julia
+xsd = XMLSchema(url) # parse an XSD schema file or URL
+
+isvalid = validate(xmlfile, schema) # validate an XML file against a previously loaded XSD schema
+isvalid = validate(doc, schema) # validate a LightXML XML Document against a previously loaded XSD schema
+isvalid = validate(url, schema) # validate a URI or file against an XSD Schema document
+isvalid = validate(element, schema) # validate a LightXML XML Node (a subtree) against an XSD Schema document
+isvalid = validate(xmlfile, schemafile) # validate an XML file or URL against a XSD schem file or URL
+```
+
+
diff --git a/src/LightXML.jl b/src/LightXML.jl
index 823895a..a84b13f 100644
--- a/src/LightXML.jl
+++ b/src/LightXML.jl
@@ -19,7 +19,10 @@ export
# document
XMLDocument, version, encoding, compression, standalone, root,
- parse_file, parse_string, save_file, set_root, create_root
+ parse_file, parse_string, save_file, set_root, create_root,
+
+ # schema
+ XMLSchema, validate
const Xchar = UInt8
const Xstr = Ptr{Xchar}
@@ -43,5 +46,6 @@ include("utils.jl")
include("nodes.jl")
include("document.jl")
include("cdata.jl")
+include("schema.jl")
end
diff --git a/src/errors.jl b/src/errors.jl
index af47540..7d5c7f3 100644
--- a/src/errors.jl
+++ b/src/errors.jl
@@ -15,3 +15,7 @@ end
struct XMLTreeError{T<:AbstractString} <: XMLError
msg::T
end
+
+struct XMLValidationError{T<:AbstractString} <: XMLError
+ msg::T
+end
diff --git a/src/schema.jl b/src/schema.jl
new file mode 100644
index 0000000..2bda3d6
--- /dev/null
+++ b/src/schema.jl
@@ -0,0 +1,89 @@
+"""
+An XML Schema Document, produced by an XML file or XMLDocument that is XML for the schema.
+"""
+mutable struct XMLSchema
+ ptr::Xptr
+ function XMLSchema(ctxt::Xptr)
+ schema = ccall((:xmlSchemaParse, libxml2), Xptr, (Xptr,), ctxt)
+ schema != C_NULL || throw(XMLValidationError("Bad XML Schema in Document"))
+ ccall((:xmlSchemaFreeParserCtxt, libxml2), Cvoid, (Xptr,), ctxt)
+ obj = new(schema)
+ finalizer(x -> Libc.free(x.ptr), obj)
+ end
+end
+
+"""
+Create an XMLSchema from a file or url
+"""
+function XMLSchema(url::String)
+ ctxt = ccall((:xmlSchemaNewParserCtxt, libxml2), Xptr, (Cstring,), url)
+ ctxt != C_NULL || throw(XMLValidationError("Bad XML Schema at " * url))
+ return XMLSchema(ctxt)
+end
+
+"""
+Create an XMLSchema from an XMLDocument
+"""
+function XMLSchema(doc::XMLDocument)
+ ctxt = ccall((:xmlSchemaNewDocParserCtxt, libxml2), Xptr, (Xptr,), doc.ptr)
+ ctxt != C_NULL || throw(XMLValidationError("Bad XML Schema in Document"))
+ return XMLSchema(ctxt)
+end
+
+"""
+Use an existing XMLschema to validate
+"""
+function _schema_valid_ctxt(f::Function, schema::XMLSchema)
+ ctxt = ccall((:xmlSchemaNewValidCtxt, libxml2), Xptr, (Xptr,), schema.ptr)
+ err = try
+ f(ctxt)
+ finally
+ Libc.free(ctxt)
+ end
+ return err
+end
+
+"""
+Validate an XMLDocument with an XMLSchema
+Returns true if valid
+"""
+function validate(xml::XMLDocument, schema::XMLSchema)
+ err = _schema_valid_ctxt(schema) do ctxt
+ ccall((:xmlSchemaValidateDoc, libxml2),
+ Cint, (Xptr, Xptr), ctxt, xml.ptr)
+ end
+ return err == 0
+end
+
+"""
+Validate an XML file or url with an XMLSchema
+Returns true if valid
+"""
+function validate(url::String, schema::XMLSchema)
+ err = _schema_valid_ctxt(schema) do ctxt
+ ccall((:xmlSchemaValidateFile, libxml2),
+ Cint, (Xptr, Cstring), ctxt, url)
+ end
+ return err == 0
+end
+
+"""
+Validate an XMLElement of an XMLDocument with an XMLSchema
+Returns true if valid
+"""
+function validate(elem::XMLElement, schema::XMLSchema)
+ err = _schema_valid_ctxt(schema) do ctxt
+ ccall((:xmlSchemaValidateOneElement, libxml2),
+ Cint, (Xptr, Xptr), ctxt, elem.node.ptr)
+ end
+ return err == 0
+end
+
+"""
+Validate an XML file or url with an XSD file or url
+Returns true if valid
+"""
+function validate(url::String, schemafile::String)
+ schema = XMLSchema(schemafile)
+ return validate(url, schema)
+end
diff --git a/test/invalid.xml b/test/invalid.xml
new file mode 100644
index 0000000..57970dc
--- /dev/null
+++ b/test/invalid.xml
@@ -0,0 +1,24 @@
+
+
+
+ John Smith
+
+ Ola Nordmann
+ Langgt 23
+ 4000 Stavanger
+ Norway
+
+ -
+ Empire Burlesque, Gold Ribbon
+ Special Edition
+ 1
+ 10.90
+
+ -
+ Hide your heart
+ 1
+ 9.90
+
+
diff --git a/test/runtests.jl b/test/runtests.jl
index d7f2b6f..9ff12a4 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,7 +1,7 @@
using LightXML
using Test
-tests = ["parse", "create", "cdata", "pi"]
+tests = ["parse", "create", "cdata", "pi", "validate"]
for t in tests
fpath = "$t.jl"
diff --git a/test/valid.xml b/test/valid.xml
new file mode 100644
index 0000000..7a6140b
--- /dev/null
+++ b/test/valid.xml
@@ -0,0 +1,24 @@
+
+
+
+ John Smith
+
+ Ola Nordmann
+ Langgt 23
+ 4000 Stavanger
+ Norway
+
+ -
+ Empire Burlesque
+ Special Edition
+ 1
+ 10.90
+
+ -
+ Hide your heart
+ 1
+ 9.90
+
+
diff --git a/test/valid.xsd b/test/valid.xsd
new file mode 100644
index 0000000..5209496
--- /dev/null
+++ b/test/valid.xsd
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/validate.jl b/test/validate.jl
new file mode 100644
index 0000000..55d56c6
--- /dev/null
+++ b/test/validate.jl
@@ -0,0 +1,16 @@
+@testset "XML Validation with XSD" begin
+
+ @test validate("valid.xml", "valid.xsd")
+ @test validate("invalid.xml", "valid.xsd") == false
+
+ doc = parse_file("valid.xml")
+ schema = XMLSchema("valid.xsd")
+
+ @test validate("valid.xml", schema)
+ @test validate("invalid.xml", schema) == false
+
+ @test validate(doc, schema)
+
+ @test validate(root(doc), schema)
+
+end