Skip to content
Merged
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
3 changes: 2 additions & 1 deletion spml/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
</configuration>
</plugin>

<!-- XML validation against DTD -->
<!-- XML validation against XSD -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
Expand All @@ -203,6 +203,7 @@
<include>*.xml</include>
</includes>
<validating>true</validating>
<schemaLanguage>http://www.w3.org/2001/XMLSchema</schemaLanguage>
</validationSet>
</validationSets>
</configuration>
Expand Down
78 changes: 53 additions & 25 deletions spml/src/main/java/info/jab/xml/CursorRuleGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
import java.io.StringWriter;
import java.util.Objects;
import java.util.Optional;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.EntityResolver;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;

/**
Expand All @@ -21,7 +25,7 @@
*/
public final class CursorRuleGenerator {

private static final String DTD_FILE_NAME = "system-prompt.dtd";
private static final String XSD_FILE_NAME = "system-prompt.xsd";

// ===============================================================
// PUBLIC API - Entry point for cursor rule generation
Expand Down Expand Up @@ -64,17 +68,28 @@ private Optional<InputStream> loadResource(String fileName) {
}

/**
* Step 2: Creates SAXSource with custom EntityResolver.
* Pure function that creates immutable SAXSource using modern SAX API.
* Step 2: Creates SAXSource with XSD validation.
* Pure function that creates immutable SAXSource with schema validation.
*/
private SAXSource createSaxSource(TransformationSources sources) {
try {
// Modern approach: Use SAXParserFactory instead of deprecated XMLReaderFactory
XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
xmlReader.setEntityResolver(new ResourceEntityResolver());
// Create SAX parser factory with namespace awareness and validation
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(false); // We'll use schema validation instead

// Load XSD schema
Optional<Schema> schema = loadXsdSchema();
if (schema.isPresent()) {
factory.setSchema(schema.get());
}

XMLReader xmlReader = factory.newSAXParser().getXMLReader();
xmlReader.setErrorHandler(new ValidationErrorHandler());

return new SAXSource(xmlReader, new InputSource(sources.xmlStream()));
} catch (SAXException | ParserConfigurationException e) {
throw new RuntimeException("Failed to create SAX source with modern XMLReader API", e);
throw new RuntimeException("Failed to create SAX source with XSD validation", e);
}
}

Expand Down Expand Up @@ -125,27 +140,40 @@ private record TransformationSources(InputStream xmlStream, InputStream xslStrea
}

/**
* Custom EntityResolver as functional interface implementation.
* Used by createSaxSource to resolve DTD references.
* Loads XSD schema from classpath for validation.
* Returns Optional to handle missing schema gracefully.
*/
private static final class ResourceEntityResolver implements EntityResolver {
private Optional<Schema> loadXsdSchema() {
return loadResource(XSD_FILE_NAME)
.map(xsdStream -> {
try {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
return schemaFactory.newSchema(new StreamSource(xsdStream));
} catch (SAXException e) {
throw new RuntimeException("Failed to load XSD schema: " + XSD_FILE_NAME, e);
}
});
}

/**
* Custom ErrorHandler for XSD validation errors.
* Provides better error reporting for validation issues.
*/
private static final class ValidationErrorHandler implements ErrorHandler {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
// Only handle system IDs we can resolve; return null for default SAX behavior otherwise
return Optional.ofNullable(systemId)
.filter(id -> id.endsWith(DTD_FILE_NAME))
.flatMap(this::loadDtdFromClasspath)
.orElse(null); // SAX contract: null means "use default resolution"
public void warning(SAXParseException exception) throws SAXException {
// Log warning in a real application
System.err.println("XSD Validation Warning: " + exception.getMessage());
}

private Optional<InputSource> loadDtdFromClasspath(String systemId) {
return Optional.ofNullable(
getClass().getClassLoader().getResourceAsStream(DTD_FILE_NAME)
).map(dtdStream -> {
InputSource inputSource = new InputSource(dtdStream);
inputSource.setSystemId(systemId);
return inputSource;
});
@Override
public void error(SAXParseException exception) throws SAXException {
throw new SAXException("XSD Validation Error: " + exception.getMessage(), exception);
}

@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw new SAXException("XSD Validation Fatal Error: " + exception.getMessage(), exception);
}
}
}
6 changes: 3 additions & 3 deletions spml/src/main/resources/100-java-checklist-guide.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE system-prompt SYSTEM "system-prompt.dtd">

<system-prompt id="100-java-checklist-guide" version="1.0">
<system-prompt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="system-prompt.xsd"
id="100-java-checklist-guide" version="1.0">
<metadata>
<description></description>
<globs></globs>
Expand Down
6 changes: 3 additions & 3 deletions spml/src/main/resources/110-java-maven-best-practices.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE system-prompt SYSTEM "system-prompt.dtd">

<system-prompt id="110-java-maven-best-practices" version="1.0">
<system-prompt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="system-prompt.xsd"
id="110-java-maven-best-practices" version="1.0">
<metadata>
<description>Maven Best Practices</description>
<globs>pom.xml</globs>
Expand Down
6 changes: 3 additions & 3 deletions spml/src/main/resources/112-java-maven-documentation.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE system-prompt SYSTEM "system-prompt.dtd">

<system-prompt id="112-java-maven-documentation" version="1.0">
<system-prompt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="system-prompt.xsd"
id="112-java-maven-documentation" version="1.0">
<metadata>
<description>Create README-DEV.md with information about how to use the Maven project</description>
<globs>pom.xml</globs>
Expand Down
Loading
Loading