001package edu.pdx.cs410J.grader.gradebook;
002
003import org.w3c.dom.*;
004import org.xml.sax.*;
005
006import javax.xml.transform.*;
007import javax.xml.transform.dom.DOMSource;
008import javax.xml.transform.stream.StreamResult;
009import java.io.*;
010import java.net.URL;
011import java.time.format.DateTimeFormatter;
012import java.util.ArrayList;
013import java.util.List;
014
015/**
016 * This class contains fields and methods that are useful when dealing
017 * with XML data.
018 */
019public class XmlHelper implements EntityResolver, ErrorHandler {
020
021  /** The System ID for the Grade Book DTD */
022  protected static final String systemID = 
023    "http://www.cs.pdx.edu/~whitlock/dtds/gradebook.dtd";
024
025  /** The Public ID for the Grade Bookd DTD */
026  protected static final String publicID = 
027    "-//Portland State University//DTD CS410J Grade Book//EN";
028
029  protected static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
030
031  public static byte[] getBytesForXmlDocument(Document xmlDoc) throws TransformerException {
032      ByteArrayOutputStream baos = new ByteArrayOutputStream();
033      PrintWriter pw =
034        new PrintWriter(new OutputStreamWriter(baos), true);
035      writeXmlToPrintWriter(xmlDoc, pw);
036      return baos.toByteArray();
037    }
038
039  ////////////////////  EntityResolver Methods  //////////////////
040
041  /**
042   * Attempt to resolve the external entity (such as a DTD) described
043   * by the given public and system ID.  The external entity is
044   * returned as a <code>InputSource</code>
045   */
046  @Override
047  public InputSource resolveEntity (String publicId, String systemId)
048    throws SAXException, IOException {
049
050    if (publicId != null && publicId.equals(XmlHelper.publicID)) {
051      // We're resolving the external entity for the Grade Book's
052      // DTD.  Check to see if its in the jar file.  This way we don't
053      // need to go all the way to the website to find the DTD.
054      String location = "/edu/pdx/cs410J/grader/gradebook.dtd";
055      InputStream stream =
056        this.getClass().getResourceAsStream(location);
057      if (stream != null) {
058        return new InputSource(stream);
059      }
060    }
061
062    // Try to access the DTD using the URL
063    try {
064      URL url = new URL(systemId);
065      InputStream stream = url.openStream();
066      return new InputSource(stream);
067
068    } catch (Exception ex) {
069      return null;
070    }
071  }
072
073  //////////////////  ErrorHandler Methods  ////////////////////////
074  
075  @Override
076  public void warning(SAXParseException ex) throws SAXException {
077    // Most warnings are annoying
078
079//      String s = "Warning while parsing XML (" + ex.getLineNumber() +
080//        ":" + ex.getColumnNumber() + "): " + ex.getMessage();
081//      System.err.println(s);
082  }
083
084  @Override
085  public void error(SAXParseException ex) throws SAXException {
086    String s = "Error while parsing XML (" + ex.getLineNumber() +
087      ":" + ex.getColumnNumber() + "): " + ex.getMessage();
088    throw new SAXException(s);
089  }
090  
091  @Override
092  public void fatalError(SAXParseException ex) throws SAXException {
093    String s = "Fatal error while parsing XML (" + ex.getLineNumber()
094      + ":" + ex.getColumnNumber() + "): " + ex.getMessage();
095    throw new SAXException(s);
096  }
097
098  ///////////////////  Other Helper Methods  ///////////////////////
099
100  /**
101   * Extracts a bunch of notes from an <code>Element</code>
102   */
103  protected static List<String> extractNotesFrom(Element element) {
104    List<String> list = new ArrayList<>();
105
106    NodeList children = element.getChildNodes();
107    for (int i = 0; i < children.getLength(); i++) {
108      Node node = children.item(i);
109      if (!(node instanceof Element)) {
110        continue;
111      }
112
113      Element child = (Element) node;
114      if (child.getTagName().equals("note")) {
115        list.add(extractTextFrom(child));
116      }
117    }
118
119    return list;
120  }
121
122  /**
123   * Extracts the text from an <code>Element</code>.
124   */
125  protected static String extractTextFrom(Element element) {
126    Text text = (Text) element.getFirstChild();
127    return (text == null ? "" : text.getData());
128  }
129
130  static void writeXmlToPrintWriter(Document doc, PrintWriter pw) throws TransformerException {
131    Source src = new DOMSource(doc);
132    Result res = new StreamResult(pw);
133
134    TransformerFactory xFactory = TransformerFactory.newInstance();
135    Transformer xform = xFactory.newTransformer();
136    xform.setOutputProperty(OutputKeys.INDENT, "yes");
137    xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "0");
138    xform.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemID);
139    xform.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, publicID);
140
141    // Suppress warnings about "Declared encoding not matching
142    // actual one
143    xform.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
144
145    xform.transform(src, res);
146  }
147}