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