Skip to content

Add XSD Schema validation #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```


6 changes: 5 additions & 1 deletion src/LightXML.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -43,5 +46,6 @@ include("utils.jl")
include("nodes.jl")
include("document.jl")
include("cdata.jl")
include("schema.jl")

end
4 changes: 4 additions & 0 deletions src/errors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ end
struct XMLTreeError{T<:AbstractString} <: XMLError
msg::T
end

struct XMLValidationError{T<:AbstractString} <: XMLError
msg::T
end
89 changes: 89 additions & 0 deletions src/schema.jl
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions test/invalid.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>

<shiporder orderid="889923"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="shiporder.xsd">
<orderperson>John Smith</orderperson>
<shipto>
<name>Ola Nordmann</name>
<address>Langgt 23</address>
<city>4000 Stavanger</city>
<country>Norway</country>
</shipto>
<item>
<titles>Empire Burlesque, Gold Ribbon</titles>
<note>Special Edition</note>
<quantity>1</quantity>
<price>10.90</price>
</item>
<item>
<title>Hide your heart</title>
<quantity>1</quantity>
<price>9.90</price>
</item>
</shiporder>
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
24 changes: 24 additions & 0 deletions test/valid.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>

<shiporder orderid="889923"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="shiporder.xsd">
<orderperson>John Smith</orderperson>
<shipto>
<name>Ola Nordmann</name>
<address>Langgt 23</address>
<city>4000 Stavanger</city>
<country>Norway</country>
</shipto>
<item>
<title>Empire Burlesque</title>
<note>Special Edition</note>
<quantity>1</quantity>
<price>10.90</price>
</item>
<item>
<title>Hide your heart</title>
<quantity>1</quantity>
<price>9.90</price>
</item>
</shiporder>
33 changes: 33 additions & 0 deletions test/valid.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="shiporder">
<xs:complexType>
<xs:sequence>
<xs:element name="orderperson" type="xs:string"/>
<xs:element name="shipto">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="address" type="xs:string"/>
<xs:element name="city" type="xs:string"/>
<xs:element name="country" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="item" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="note" type="xs:string" minOccurs="0"/>
<xs:element name="quantity" type="xs:positiveInteger"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="orderid" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>

</xs:schema>
16 changes: 16 additions & 0 deletions test/validate.jl
Original file line number Diff line number Diff line change
@@ -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