| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.nbbuild; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.net.URL; |
| import java.text.DateFormat; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.transform.OutputKeys; |
| import javax.xml.transform.Result; |
| import javax.xml.transform.Source; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerConfigurationException; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.URIResolver; |
| import javax.xml.transform.dom.DOMResult; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.transform.stream.StreamSource; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.EntityResolver; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| /** |
| * Task to process Arch questions and answers document. |
| * @author Jaroslav Tulach, Jesse Glick |
| */ |
| public class Arch extends Task implements ErrorHandler, EntityResolver, URIResolver { |
| |
| /** map from String ids -> Elements */ |
| private Map<String,Element> answers; |
| private Map<String,Element> questions; |
| /** exception during parsing */ |
| private SAXParseException parseException; |
| |
| public Arch() { |
| } |
| |
| // |
| // itself |
| // |
| |
| private File questionsFile; |
| /** Questions and answers file */ |
| public void setAnswers (File f) { |
| questionsFile = f; |
| } |
| |
| |
| private File output; |
| /** Output file |
| */ |
| public void setOutput (File f) { |
| output = f; |
| } |
| |
| // For use when generating API documentation: |
| private String stylesheet = null; |
| public void setStylesheet(String s) { |
| stylesheet = s; |
| } |
| private String overviewlink = null; |
| public void setOverviewlink(String s) { |
| overviewlink = s; |
| } |
| private String footer = null; |
| public void setFooter(String s) { |
| footer = s; |
| } |
| private File xsl = null; |
| public void setXSL (File xsl) { |
| this.xsl = xsl; |
| } |
| |
| private File apichanges = null; |
| public void setApichanges (File apichanges) { |
| this.apichanges = apichanges; |
| } |
| |
| private File project = null; |
| public void setProject (File x) { |
| this.project = x; |
| } |
| |
| /** Run the conversion */ |
| public void execute () throws BuildException { |
| if ( questionsFile == null ) { |
| throw new BuildException ("questions file must be provided"); |
| } |
| |
| if ( output == null ) { |
| throw new BuildException ("output file must be specified"); |
| } |
| |
| boolean generateTemplate = !questionsFile.exists(); |
| |
| if ( |
| !generateTemplate && |
| output.exists() && |
| questionsFile.lastModified() <= output.lastModified() && |
| this.getProject().getProperty ("arch.generate") == null |
| ) { |
| // nothing needs to be generated. everything is up to date |
| return; |
| } |
| |
| |
| Document q; |
| Source qSource; |
| DocumentBuilderFactory factory; |
| DocumentBuilder builder; |
| try { |
| factory = DocumentBuilderFactory.newInstance (); |
| factory.setValidating(!generateTemplate && !"true".equals(this.getProject().getProperty ("arch.private.disable.validation.for.test.purposes"))); // NOI18N |
| |
| builder = factory.newDocumentBuilder(); |
| builder.setErrorHandler(this); |
| builder.setEntityResolver(this); |
| |
| if (generateTemplate) { |
| try (InputStream resource = Arch.class.getResourceAsStream("Arch-api-questions.xml")) { |
| q = builder.parse(resource); |
| qSource = new DOMSource (q); |
| } |
| } else { |
| q = builder.parse (questionsFile); |
| qSource = new DOMSource (q); |
| } |
| |
| if (parseException != null) { |
| throw parseException; |
| } |
| |
| } catch (SAXParseException ex) { |
| log(ex.getSystemId() + ":" + ex.getLineNumber() + ": " + ex.getLocalizedMessage(), Project.MSG_ERR); |
| throw new BuildException(questionsFile.getAbsolutePath() + " is malformed or invalid", ex, getLocation()); |
| } catch (Exception ex) { |
| throw new BuildException ("File " + questionsFile + " cannot be parsed: " + ex.getLocalizedMessage(), ex, getLocation()); |
| } |
| |
| questions = readElements (q, "question"); |
| |
| String questionsVersion; |
| { |
| NodeList apiQuestions = q.getElementsByTagName("api-questions"); |
| if (apiQuestions.getLength () != 1) { |
| throw new BuildException ("No element api-questions"); |
| } |
| questionsVersion = ((Element)apiQuestions.item (0)).getAttribute ("version"); |
| if (questionsVersion == null) { |
| throw new BuildException ("Element api-questions does not have attribute version"); |
| } |
| } |
| |
| if (questions.size () == 0) { |
| throw new BuildException ("There are no <question> elements in the file!"); |
| } |
| |
| if (generateTemplate) { |
| log ("Input file " + questionsFile + " does not exist. Generating it with skeleton answers."); |
| try { |
| SortedSet<String> s = new TreeSet<>(questions.keySet()); |
| generateTemplateFile(questionsVersion, s); |
| } catch (IOException ex) { |
| throw new BuildException (ex); |
| } |
| |
| return; |
| } |
| |
| answers = readElements (q, "answer"); |
| |
| |
| { |
| //System.out.println("doc:\n" + q.getDocumentElement()); |
| |
| // version of answers and version of questions |
| NodeList apiAnswers = q.getElementsByTagName("api-answers"); |
| |
| if (apiAnswers.getLength() != 1) { |
| throw new BuildException ("No element api-answers"); |
| } |
| |
| String answersVersion = ((Element)apiAnswers.item (0)).getAttribute ("question-version"); |
| |
| if (answersVersion == null) { |
| throw new BuildException ("Element api-answers does not have attribute question-version"); |
| } |
| |
| if (!answersVersion.equals(questionsVersion)) { |
| String msg = questionsFile.getAbsolutePath() + ": answers were created for questions version \"" + answersVersion + "\" but current version of questions is \"" + questionsVersion + "\""; |
| if ("false".equals (this.getProject().getProperty("arch.warn"))) { |
| throw new BuildException (msg); |
| } else { |
| log (msg, Project.MSG_WARN); |
| } |
| } |
| } |
| |
| { |
| // check all answers have their questions |
| SortedSet<String> s = new TreeSet<>(questions.keySet()); |
| s.removeAll (answers.keySet ()); |
| if (!s.isEmpty()) { |
| if ("true".equals (this.getProject().getProperty ("arch.generate"))) { |
| log ("Missing answers to questions: " + s); |
| log ("Generating the answers to end of file " + questionsFile); |
| try { |
| generateMissingQuestions(questionsVersion, s); |
| } catch (IOException ex) { |
| throw new BuildException (ex); |
| } |
| qSource = new StreamSource (questionsFile); |
| try { |
| q = builder.parse(questionsFile); |
| } catch (IOException | SAXException ex) { |
| throw new BuildException(ex); |
| } |
| } else { |
| log ( |
| questionsFile.getAbsolutePath() + ": some questions have not been answered: " + s + "\n" + |
| "Run with -Darch.generate=true to add missing questions into the end of question file" |
| , Project.MSG_WARN); |
| } |
| } |
| } |
| |
| |
| if (apichanges != null) { |
| // read also apichanges and add them to the document |
| log("Reading apichanges from " + apichanges); |
| |
| Document api; |
| try { |
| api = builder.parse (apichanges); |
| } catch (SAXParseException ex) { |
| log(ex.getSystemId() + ":" + ex.getLineNumber() + ": " + ex.getLocalizedMessage(), Project.MSG_ERR); |
| throw new BuildException(apichanges.getAbsolutePath() + " is malformed or invalid", ex, getLocation()); |
| } catch (Exception ex) { |
| throw new BuildException ("File " + apichanges + " cannot be parsed: " + ex.getLocalizedMessage(), ex, getLocation()); |
| } |
| |
| NodeList node = api.getElementsByTagName("apichanges"); |
| if (node.getLength() != 1) { |
| throw new BuildException("Expected one element <apichanges/> in " + apichanges + "but was: " + node.getLength()); |
| } |
| |
| Node n = node.item(0); |
| Node el = q.getElementsByTagName("api-answers").item(0); |
| |
| el.appendChild(q.importNode(n, true)); |
| |
| |
| qSource = new DOMSource(q); |
| qSource.setSystemId(questionsFile.toURI().toString()); |
| } |
| |
| |
| if (project != null) { |
| // read also project file and apply transformation on defaultanswer tags |
| log("Reading project from " + project); |
| |
| |
| |
| Document prj; |
| try { |
| DocumentBuilderFactory fack = DocumentBuilderFactory.newInstance(); |
| fack.setNamespaceAware(false); |
| prj = fack.newDocumentBuilder().parse (project); |
| } catch (SAXParseException ex) { |
| log(ex.getSystemId() + ":" + ex.getLineNumber() + ": " + ex.getLocalizedMessage(), Project.MSG_ERR); |
| throw new BuildException(project.getAbsolutePath() + " is malformed or invalid", ex, getLocation()); |
| } catch (Exception ex) { |
| throw new BuildException ("File " + project + " cannot be parsed: " + ex.getLocalizedMessage(), ex, getLocation()); |
| } |
| |
| // enhance the project document with info about stability and logical name of an API |
| // use arch.code-name-base.name and arch.code-name-base.category |
| // to modify regular: |
| // <dependency> |
| // <code-name-base>org.openide.util</code-name-base> |
| // <build-prerequisite/> |
| // <compile-dependency/> |
| // <run-dependency> |
| // <specification-version>6.2</specification-version> |
| // </run-dependency> |
| // </dependency> |
| // to include additional items like: |
| // <dependency> |
| // <code-name-base>org.openide.util</code-name-base> |
| // <api-name>UtilitiesAPI</api-name> |
| // <api-category>official</api-category> |
| // <build-prerequisite/> |
| // <compile-dependency/> |
| // <run-dependency> |
| // <specification-version>6.2</specification-version> |
| // </run-dependency> |
| // </dependency> |
| |
| { |
| NodeList deps = prj.getElementsByTagName("code-name-base"); |
| for (int i = 0; i < deps.getLength(); i++) { |
| Node name = deps.item(i); |
| String api = name.getChildNodes().item(0).getNodeValue(); |
| String human = this.getProject().getProperty("arch." + api + ".name"); |
| if (human != null) { |
| if (human.equals("")) { |
| throw new BuildException("Empty name for " + api + " from " + project); |
| } |
| |
| Element e = prj.createElement("api-name"); |
| e.appendChild(prj.createTextNode(human)); |
| name.getParentNode().insertBefore(e, name); |
| } |
| String category = this.getProject().getProperty("arch." + api + ".category"); |
| if (category != null) { |
| if (category.equals("")) { |
| throw new BuildException("Empty category for " + api + " from " + project); |
| } |
| Element e = prj.createElement("api-category"); |
| e.appendChild(prj.createTextNode(category)); |
| name.getParentNode().insertBefore(e, name); |
| } |
| |
| } |
| } |
| |
| DOMSource prjSrc = new DOMSource(prj); |
| |
| NodeList node = prj.getElementsByTagName("project"); |
| if (node.getLength() != 1) { |
| throw new BuildException("Expected one element <project/> in " + project + "but was: " + node.getLength()); |
| } |
| |
| NodeList list= q.getElementsByTagName("answer"); |
| for (int i = 0; i < list.getLength(); i++) { |
| Node n = list.item(i); |
| String id = n.getAttributes().getNamedItem("id").getNodeValue(); |
| URL u = Arch.class.getResource("Arch-default-" + id + ".xsl"); |
| if (u != null) { |
| log("Found default answer to " + id + " question", Project.MSG_VERBOSE); |
| Node defaultAnswer = findDefaultAnswer(n); |
| if (defaultAnswer != null && |
| "none".equals(defaultAnswer.getAttributes().getNamedItem("generate").getNodeValue()) |
| ) { |
| log("Skipping answer as there is <defaultanswer generate='none'", Project.MSG_VERBOSE); |
| // ok, this default answer is requested to be skipped |
| continue; |
| } |
| |
| DOMResult res = new DOMResult(q.createElement("p")); |
| try { |
| StreamSource defXSL = new StreamSource(u.openStream()); |
| |
| TransformerFactory fack = TransformerFactory.newInstance(); |
| Transformer t = fack.newTransformer(defXSL); |
| t.transform(prjSrc, res); |
| } catch (IOException | TransformerException ex) { |
| throw new BuildException (ex); |
| } |
| |
| if (defaultAnswer != null) { |
| log("Replacing default answer", Project.MSG_VERBOSE); |
| defaultAnswer.getParentNode().replaceChild(res.getNode(), defaultAnswer); |
| } else { |
| log("Adding default answer to the end of previous one", Project.MSG_VERBOSE); |
| Element para = q.createElement("p"); |
| para.appendChild(q.createTextNode("The default answer to this question is:")); |
| para.appendChild(q.createComment("If you do not want default answer to be generated you can use <defaultanswer generate='none' /> here")); |
| para.appendChild(q.createElement("br")); |
| para.appendChild(res.getNode()); |
| n.appendChild(para); |
| } |
| } |
| } |
| |
| |
| qSource = new DOMSource(q); |
| qSource.setSystemId(questionsFile.toURI().toString()); |
| } |
| |
| if (this.getProject().getProperty("javadoc.title") != null) { |
| // verify we have the api-answers@module and possibly add it |
| NodeList deps = q.getElementsByTagName("api-answers"); |
| if (deps.getLength() != 1) { |
| throw new BuildException("Strange number of api-answers elements: " + deps.getLength()); |
| } |
| |
| Node module = deps.item(0).getAttributes().getNamedItem("module"); |
| if (module == null) { |
| Attr attr = q.createAttribute("module"); |
| deps.item(0).getAttributes().setNamedItem(attr); |
| attr.setValue(this.getProject().getProperty("javadoc.title")); |
| } |
| |
| qSource = new DOMSource(q); |
| qSource.setSystemId(questionsFile.toURI().toString()); |
| } |
| |
| |
| // apply the transform operation |
| try { |
| StreamSource ss; |
| String file = this.xsl != null ? this.xsl.toString() : getProject().getProperty ("arch.xsl"); |
| |
| if (file != null) { |
| log ("Using " + file + " as the XSL stylesheet"); |
| ss = new StreamSource (file); |
| } else { |
| ss = new StreamSource ( |
| getClass ().getResourceAsStream ("Arch.xsl") |
| ); |
| } |
| |
| log("Transforming " + questionsFile + " into " + output); |
| |
| TransformerFactory trans; |
| trans = TransformerFactory.newInstance(); |
| trans.setURIResolver(this); |
| Transformer t = trans.newTransformer(ss); |
| try (OutputStream os = new BufferedOutputStream (new FileOutputStream (output))) { |
| StreamResult r = new StreamResult (os); |
| if (stylesheet == null) { |
| stylesheet = this.getProject ().getProperty ("arch.stylesheet"); |
| } |
| if (stylesheet != null) { |
| t.setParameter("arch.stylesheet", stylesheet); |
| } |
| if (overviewlink != null) { |
| t.setParameter("arch.overviewlink", overviewlink); |
| } |
| if (footer != null) { |
| t.setParameter("arch.footer", footer); |
| } |
| t.setParameter("arch.answers.date", DateFormat.getDateInstance().format(new Date(questionsFile.lastModified()))); |
| |
| String archTarget = output.toString(); |
| int slash = archTarget.lastIndexOf(File.separatorChar); |
| if (slash > 0) { |
| archTarget = archTarget.substring (slash + 1); |
| } |
| String archPref = getProject ().getProperty ("arch.target"); |
| if (archPref != null) { |
| archTarget = archPref + "/" + archTarget; |
| } |
| |
| t.setParameter("arch.target", archTarget); |
| String when = getProject().getProperty("arch.when"); |
| if (when != null) { |
| t.setParameter("arch.when", when); |
| } |
| t.transform(qSource, r); |
| } |
| } catch (IOException | TransformerException ex) { |
| throw new BuildException (ex); |
| } |
| } |
| |
| private void generateMissingQuestions(String version, SortedSet<String> missing) throws IOException, BuildException { |
| StringBuffer sb = new StringBuffer(); |
| InputStreamReader is = new InputStreamReader(new FileInputStream(questionsFile.toString())); |
| char[] arr = new char[4096]; |
| for (;;) { |
| int len = is.read(arr); |
| if (len == -1) break; |
| |
| sb.append(arr, 0, len); |
| } |
| |
| int indx = sb.indexOf("</api-answers>"); |
| if (indx == -1) { |
| throw new BuildException("There is no </api-answers> in " + questionsFile); |
| } |
| |
| sb.delete (indx, indx + "</api-answers>".length()); |
| |
| Matcher m = Pattern.compile("question-version='([0-9\\.]*)'").matcher(sb); |
| if (m.find()) { |
| sb.delete(m.start(1), m.end(1)); |
| sb.insert(m.start(1), version); |
| } |
| |
| try (Writer w = new OutputStreamWriter (new FileOutputStream (questionsFile.toString ()))) { |
| w.write(sb.toString()); |
| writeQuestions (w, missing); |
| w.write("</api-answers>\n"); |
| } |
| } |
| |
| private void writeQuestions(Writer w, SortedSet<String> missing) throws IOException { |
| for (String s : missing) { |
| Element n = questions.get(s); |
| |
| //w.write("\n\n<!-- Question: " + s + "\n"); |
| w.write("\n\n<!--\n "); |
| w.write(elementToString(n)); |
| w.write("\n-->\n"); |
| |
| URL u = Arch.class.getResource("Arch-default-" + s + ".xsl"); |
| if (u != null) { |
| // there is default answer |
| w.write(" <answer id=\"" + s + "\">\n <defaultanswer generate='here' />\n </answer>\n\n"); |
| } else { |
| w.write(" <answer id=\"" + s + "\">\n <p>\n XXX no answer for " + s + "\n </p>\n </answer>\n\n"); |
| } |
| } |
| } |
| |
| |
| private static String findNbRoot (File f) { |
| StringBuffer result = new StringBuffer (); |
| f = f.getParentFile(); |
| |
| while (f != null) { |
| File x = new File (f, |
| "nbbuild" + File.separatorChar + |
| "antsrc" + File.separatorChar + |
| "org" + File.separatorChar + |
| "netbeans" + File.separatorChar + |
| "nbbuild" + File.separatorChar + |
| "Arch.dtd" |
| ); |
| if (x.exists ()) { |
| return result.toString(); |
| } |
| result.append("../"); // URI, so pathsep is / |
| f = f.getParentFile(); |
| } |
| return null; |
| } |
| |
| private void generateTemplateFile(String versionOfQuestions, SortedSet<String> missing) throws IOException { |
| String nbRoot = findNbRoot(questionsFile); |
| if (nbRoot == null) { |
| nbRoot = "http://hg.netbeans.org/main/raw-file/tip/"; |
| } |
| |
| try (Writer w = new FileWriter (questionsFile)) { |
| w.write ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
| w.write ("<!DOCTYPE api-answers PUBLIC \"-//NetBeans//DTD Arch Answers//EN\" \""); w.write (nbRoot); w.write ("nbbuild/antsrc/org/netbeans/nbbuild/Arch.dtd\" [\n"); |
| w.write (" <!ENTITY api-questions SYSTEM \""); w.write (nbRoot); w.write ("nbbuild/antsrc/org/netbeans/nbbuild/Arch-api-questions.xml\">\n"); |
| w.write ("]>\n"); |
| w.write ("\n"); |
| w.write ("<api-answers\n"); |
| w.write (" question-version=\""); w.write (versionOfQuestions); w.write ("\"\n"); |
| w.write (" author=\"yourname@netbeans.org\"\n"); |
| w.write (">\n\n"); |
| w.write (" &api-questions;\n"); |
| |
| writeQuestions (w, missing); |
| |
| w.write ("</api-answers>\n"); |
| } |
| } |
| |
| private static Map<String,Element> readElements (Document q, String name) { |
| Map<String,Element> map = new HashMap<>(); |
| |
| NodeList list = q.getElementsByTagName(name); |
| for (int i = 0; i < list.getLength(); i++) { |
| Node n = list.item (i).getAttributes().getNamedItem("id"); |
| if (n == null) { |
| throw new BuildException ("Question without id tag"); |
| } |
| String id = n.getNodeValue(); |
| |
| map.put(id, (Element) list.item(i)); |
| } |
| |
| return map; |
| } |
| |
| public void error(SAXParseException exception) throws SAXException { |
| if (parseException != null) { |
| log(parseException.getSystemId() + ":" + parseException.getLineNumber() + ": " + parseException.getLocalizedMessage(), Project.MSG_ERR); |
| } |
| parseException = exception; |
| } |
| |
| public void fatalError(SAXParseException exception) throws SAXException { |
| throw exception; |
| } |
| |
| public void warning(SAXParseException exception) throws SAXException { |
| if (exception.getLocalizedMessage().startsWith("Using original entity definition for")) { |
| // Pointless message, always logged when using XHTML. Ignore. |
| return; |
| } |
| log(exception.getSystemId() + ":" + exception.getLineNumber() + ": " + exception.getLocalizedMessage(), Project.MSG_WARN); |
| } |
| |
| private static String elementToString(Element e) throws IOException { |
| try { |
| Transformer t = TransformerFactory.newInstance().newTransformer(); |
| t.setOutputProperty(OutputKeys.INDENT, "no"); // NOI18N |
| t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); // NOI18N |
| Source source = new DOMSource(e); |
| StringWriter w = new StringWriter(); |
| Result result = new StreamResult(w); |
| t.transform(source, result); |
| return w.toString(); |
| } catch (Exception x) { |
| throw (IOException)new IOException(x.toString()).initCause(x); |
| } |
| } |
| |
| private static Node findDefaultAnswer(Node n) { |
| if (n.getNodeName().equals ("defaultanswer")) { |
| return n; |
| } |
| |
| NodeList arr = n.getChildNodes(); |
| for (int i = 0; i < arr.getLength(); i++) { |
| Node found = findDefaultAnswer(arr.item(i)); |
| if (found != null) { |
| return found; |
| } |
| } |
| return null; |
| } |
| |
| private static Set<String> localDTDs; |
| static { |
| localDTDs = new HashSet<>(); |
| localDTDs.add("Arch.dtd"); |
| localDTDs.add("Arch-api-questions.xml"); |
| localDTDs.add("xhtml1-strict.dtd"); |
| localDTDs.add("xhtml-lat1.ent"); |
| localDTDs.add("xhtml-special.ent"); |
| localDTDs.add("xhtml-symbol.ent"); |
| } |
| |
| public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { |
| log("publicId: " + publicId + " systemId: " + systemId, Project.MSG_VERBOSE); |
| |
| int idx = systemId.lastIndexOf('/'); |
| String last = systemId.substring(idx + 1); |
| |
| if (localDTDs.contains(last)) { |
| log("Resolved to embedded DTD"); |
| InputSource is = new InputSource(Arch.class.getResourceAsStream(last)); |
| is.setSystemId(systemId); |
| return is; |
| } else { |
| log("Not resolved"); |
| return null; |
| } |
| } |
| |
| public Source resolve(String href, String base) throws TransformerException { |
| return null; |
| } |
| } |