/** * (The MIT License) * * Copyright (c) 2008 - 2011: * * * {Aaron Patterson}[http://tenderlovemaking.com] * * {Mike Dalessio}[http://mike.daless.io] * * {Charles Nutter}[http://blog.headius.com] * * {Sergio Arbeo}[http://www.serabe.com] * * {Patrick Mahoney}[http://polycrystal.org] * * {Yoko Harada}[http://yokolet.blogspot.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. */ package nokogiri; import static nokogiri.internals.NokogiriHelpers.adjustSystemIdIfNecessary; import static nokogiri.internals.NokogiriHelpers.getNokogiriClass; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import javax.xml.XMLConstants; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import nokogiri.internals.IgnoreSchemaErrorsErrorHandler; import nokogiri.internals.SchemaErrorHandler; import nokogiri.internals.XmlDomParserContext; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyObject; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.w3c.dom.Document; import org.w3c.dom.ls.LSInput; import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; /** * Class for Nokogiri::XML::Schema * * @author sergio * @author Yoko Harada <yokolet@gmail.com> */ @JRubyClass(name="Nokogiri::XML::Schema") public class XmlSchema extends RubyObject { private Validator validator; public XmlSchema(Ruby ruby, RubyClass klazz) { super(ruby, klazz); } /** * Create and return a copy of this object. * * @return a clone of this object */ @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } private Schema getSchema(Source source, String currentDir, String scriptFileName) throws SAXException { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); SchemaResourceResolver resourceResolver = new SchemaResourceResolver(currentDir, scriptFileName, null); schemaFactory.setResourceResolver(resourceResolver); schemaFactory.setErrorHandler(new IgnoreSchemaErrorsErrorHandler()); return schemaFactory.newSchema(source); } private void setValidator(Validator validator) { this.validator = validator; } static XmlSchema createSchemaInstance(ThreadContext context, RubyClass klazz, Source source) { Ruby runtime = context.getRuntime(); XmlSchema xmlSchema = (XmlSchema) NokogiriService.XML_SCHEMA_ALLOCATOR.allocate(runtime, klazz); xmlSchema.setInstanceVariable("@errors", runtime.newEmptyArray()); try { Schema schema = xmlSchema.getSchema(source, context.getRuntime().getCurrentDirectory(), context.getRuntime().getInstanceConfig().getScriptFileName()); xmlSchema.setValidator(schema.newValidator()); return xmlSchema; } catch (SAXException ex) { throw context.getRuntime().newRuntimeError("Could not parse document: " + ex.getMessage()); } } /* * call-seq: * from_document(doc) * * Create a new Schema from the Nokogiri::XML::Document +doc+ */ @JRubyMethod(meta=true) public static IRubyObject from_document(ThreadContext context, IRubyObject klazz, IRubyObject document) { XmlDocument doc = ((XmlDocument) ((XmlNode) document).document(context)); RubyArray errors = (RubyArray) doc.getInstanceVariable("@errors"); if (!errors.isEmpty()) { throw new RaiseException((XmlSyntaxError) errors.first()); } DOMSource source = new DOMSource(doc.getDocument()); IRubyObject uri = doc.url(context); if (!uri.isNil()) { source.setSystemId(uri.convertToString().asJavaString()); } return getSchema(context, (RubyClass)klazz, source); } private static IRubyObject getSchema(ThreadContext context, RubyClass klazz, Source source) { String moduleName = klazz.getName(); if ("Nokogiri::XML::Schema".equals(moduleName)) { return XmlSchema.createSchemaInstance(context, klazz, source); } else if ("Nokogiri::XML::RelaxNG".equals(moduleName)) { return XmlRelaxng.createSchemaInstance(context, klazz, source); } return context.getRuntime().getNil(); } @JRubyMethod(meta=true) public static IRubyObject read_memory(ThreadContext context, IRubyObject klazz, IRubyObject content) { String data = content.convertToString().asJavaString(); return getSchema(context, (RubyClass) klazz, new StreamSource(new StringReader(data))); } @JRubyMethod(visibility=Visibility.PRIVATE) public IRubyObject validate_document(ThreadContext context, IRubyObject document) { return validate_document_or_file(context, (XmlDocument)document); } @JRubyMethod(visibility=Visibility.PRIVATE) public IRubyObject validate_file(ThreadContext context, IRubyObject file) { Ruby ruby = context.getRuntime(); XmlDomParserContext ctx = new XmlDomParserContext(ruby, RubyFixnum.newFixnum(ruby, 1L)); ctx.setInputSource(context, file, context.getRuntime().getNil()); XmlDocument xmlDocument = ctx.parse(context, getNokogiriClass(ruby, "Nokogiri::XML::Document"), ruby.getNil()); return validate_document_or_file(context, xmlDocument); } IRubyObject validate_document_or_file(ThreadContext context, XmlDocument xmlDocument) { RubyArray errors = (RubyArray) this.getInstanceVariable("@errors"); ErrorHandler errorHandler = new SchemaErrorHandler(context.getRuntime(), errors); setErrorHandler(errorHandler); try { validate(xmlDocument.getDocument()); } catch(SAXException ex) { XmlSyntaxError xmlSyntaxError = (XmlSyntaxError) NokogiriService.XML_SYNTAXERROR_ALLOCATOR.allocate(context.getRuntime(), getNokogiriClass(context.getRuntime(), "Nokogiri::XML::SyntaxError")); xmlSyntaxError.setException(ex); errors.append(xmlSyntaxError); } catch (IOException ex) { throw context.getRuntime().newIOError(ex.getMessage()); } return errors; } protected void setErrorHandler(ErrorHandler errorHandler) { validator.setErrorHandler(errorHandler); } protected void validate(Document document) throws SAXException, IOException { DOMSource docSource = new DOMSource(document); validator.validate(docSource); } private class SchemaResourceResolver implements LSResourceResolver { SchemaLSInput lsInput = new SchemaLSInput(); String currentDir; String scriptFileName; //String defaultURI; SchemaResourceResolver(String currentDir, String scriptFileName, Object input) { this.currentDir = currentDir; this.scriptFileName = scriptFileName; if (input == null) return; if (input instanceof String) { lsInput.setStringData((String)input); } else if (input instanceof Reader) { lsInput.setCharacterStream((Reader)input); } else if (input instanceof InputStream) { lsInput.setByteStream((InputStream)input); } } @Override public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) { String adjusted = adjustSystemIdIfNecessary(currentDir, scriptFileName, baseURI, systemId); lsInput.setPublicId(publicId); lsInput.setSystemId(adjusted != null? adjusted : systemId); lsInput.setBaseURI(baseURI); return lsInput; } } private class SchemaLSInput implements LSInput { protected String fPublicId; protected String fSystemId; protected String fBaseSystemId; protected InputStream fByteStream; protected Reader fCharStream; protected String fData; protected String fEncoding; protected boolean fCertifiedText = false; @Override public String getBaseURI() { return fBaseSystemId; } @Override public InputStream getByteStream() { return fByteStream; } @Override public boolean getCertifiedText() { return fCertifiedText; } @Override public Reader getCharacterStream() { return fCharStream; } @Override public String getEncoding() { return fEncoding; } @Override public String getPublicId() { return fPublicId; } @Override public String getStringData() { return fData; } @Override public String getSystemId() { return fSystemId; } @Override public void setBaseURI(String baseURI) { fBaseSystemId = baseURI; } @Override public void setByteStream(InputStream byteStream) { fByteStream = byteStream; } @Override public void setCertifiedText(boolean certified) { fCertifiedText = certified; } @Override public void setCharacterStream(Reader charStream) { fCharStream = charStream; } @Override public void setEncoding(String encoding) { fEncoding = encoding; } @Override public void setPublicId(String pubId) { fPublicId = pubId; } @Override public void setStringData(String stringData) { fData = stringData; } @Override public void setSystemId(String sysId) { fSystemId = sysId; } } }