|
25 | 25 | */ |
26 | 26 | public final class CursorRuleGenerator { |
27 | 27 |
|
28 | | - private static final String DTD_FILE_NAME = "system-prompt.dtd"; |
| 28 | + private static final String XSD_FILE_NAME = "system-prompt.xsd"; |
29 | 29 |
|
30 | 30 | // =============================================================== |
31 | 31 | // PUBLIC API - Entry point for cursor rule generation |
@@ -68,17 +68,28 @@ private Optional<InputStream> loadResource(String fileName) { |
68 | 68 | } |
69 | 69 |
|
70 | 70 | /** |
71 | | - * Step 2: Creates SAXSource with custom EntityResolver. |
72 | | - * Pure function that creates immutable SAXSource using modern SAX API. |
| 71 | + * Step 2: Creates SAXSource with XSD validation. |
| 72 | + * Pure function that creates immutable SAXSource with schema validation. |
73 | 73 | */ |
74 | 74 | private SAXSource createSaxSource(TransformationSources sources) { |
75 | 75 | try { |
76 | | - // Modern approach: Use SAXParserFactory instead of deprecated XMLReaderFactory |
77 | | - XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); |
78 | | - xmlReader.setEntityResolver(new ResourceEntityResolver()); |
| 76 | + // Create SAX parser factory with namespace awareness and validation |
| 77 | + SAXParserFactory factory = SAXParserFactory.newInstance(); |
| 78 | + factory.setNamespaceAware(true); |
| 79 | + factory.setValidating(false); // We'll use schema validation instead |
| 80 | + |
| 81 | + // Load XSD schema |
| 82 | + Optional<Schema> schema = loadXsdSchema(); |
| 83 | + if (schema.isPresent()) { |
| 84 | + factory.setSchema(schema.get()); |
| 85 | + } |
| 86 | + |
| 87 | + XMLReader xmlReader = factory.newSAXParser().getXMLReader(); |
| 88 | + xmlReader.setErrorHandler(new ValidationErrorHandler()); |
| 89 | + |
79 | 90 | return new SAXSource(xmlReader, new InputSource(sources.xmlStream())); |
80 | 91 | } catch (SAXException | ParserConfigurationException e) { |
81 | | - throw new RuntimeException("Failed to create SAX source with modern XMLReader API", e); |
| 92 | + throw new RuntimeException("Failed to create SAX source with XSD validation", e); |
82 | 93 | } |
83 | 94 | } |
84 | 95 |
|
@@ -129,27 +140,40 @@ private record TransformationSources(InputStream xmlStream, InputStream xslStrea |
129 | 140 | } |
130 | 141 |
|
131 | 142 | /** |
132 | | - * Custom EntityResolver as functional interface implementation. |
133 | | - * Used by createSaxSource to resolve DTD references. |
| 143 | + * Loads XSD schema from classpath for validation. |
| 144 | + * Returns Optional to handle missing schema gracefully. |
134 | 145 | */ |
135 | | - private static final class ResourceEntityResolver implements EntityResolver { |
| 146 | + private Optional<Schema> loadXsdSchema() { |
| 147 | + return loadResource(XSD_FILE_NAME) |
| 148 | + .map(xsdStream -> { |
| 149 | + try { |
| 150 | + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); |
| 151 | + return schemaFactory.newSchema(new StreamSource(xsdStream)); |
| 152 | + } catch (SAXException e) { |
| 153 | + throw new RuntimeException("Failed to load XSD schema: " + XSD_FILE_NAME, e); |
| 154 | + } |
| 155 | + }); |
| 156 | + } |
| 157 | + |
| 158 | + /** |
| 159 | + * Custom ErrorHandler for XSD validation errors. |
| 160 | + * Provides better error reporting for validation issues. |
| 161 | + */ |
| 162 | + private static final class ValidationErrorHandler implements ErrorHandler { |
136 | 163 | @Override |
137 | | - public InputSource resolveEntity(String publicId, String systemId) throws SAXException { |
138 | | - // Only handle system IDs we can resolve; return null for default SAX behavior otherwise |
139 | | - return Optional.ofNullable(systemId) |
140 | | - .filter(id -> id.endsWith(DTD_FILE_NAME)) |
141 | | - .flatMap(this::loadDtdFromClasspath) |
142 | | - .orElse(null); // SAX contract: null means "use default resolution" |
| 164 | + public void warning(SAXParseException exception) throws SAXException { |
| 165 | + // Log warning in a real application |
| 166 | + System.err.println("XSD Validation Warning: " + exception.getMessage()); |
143 | 167 | } |
144 | 168 |
|
145 | | - private Optional<InputSource> loadDtdFromClasspath(String systemId) { |
146 | | - return Optional.ofNullable( |
147 | | - getClass().getClassLoader().getResourceAsStream(DTD_FILE_NAME) |
148 | | - ).map(dtdStream -> { |
149 | | - InputSource inputSource = new InputSource(dtdStream); |
150 | | - inputSource.setSystemId(systemId); |
151 | | - return inputSource; |
152 | | - }); |
| 169 | + @Override |
| 170 | + public void error(SAXParseException exception) throws SAXException { |
| 171 | + throw new SAXException("XSD Validation Error: " + exception.getMessage(), exception); |
| 172 | + } |
| 173 | + |
| 174 | + @Override |
| 175 | + public void fatalError(SAXParseException exception) throws SAXException { |
| 176 | + throw new SAXException("XSD Validation Fatal Error: " + exception.getMessage(), exception); |
153 | 177 | } |
154 | 178 | } |
155 | 179 | } |
0 commit comments