/*
* Copyright (C) Chaperon. All rights reserved.
* -------------------------------------------------------------------------
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package net.sourceforge.chaperon.ant;
import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.xml.serialize.Method;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import net.sourceforge.chaperon.parser.output.EventQueue;
import net.sourceforge.chaperon.parser.output.SAXEventAdapter;
import net.sourceforge.chaperon.parser.ParserException;
import net.sourceforge.chaperon.parser.Parser;
import net.sourceforge.chaperon.parser.ParserTable;
/**
* A ant task for parsing fragment in a xml file
*
* @author Stephan Michels
* @version %version%
*/
public class ChaperonFragmentParse extends MatchingTask
{
private File destDir = null;
private File baseDir = null;
private String parsertableFilename = null;
private String targetExtension = ".xml";
private Vector params = new Vector();
private File inFile = null;
private File outFile = null;
private String encoding = null;
private boolean ignorableTokens = false;
private boolean indent = false;
private FileUtils fileUtils;
private int msgLevel = Project.MSG_ERR;
private AntLogger logger;
private Parser parser;
private SAXEventAdapter adapter;
/**
* Constructs the task
*/
public ChaperonFragmentParse()
{
fileUtils = FileUtils.newFileUtils();
}
/**
* Executes the task
*
* @throws BuildException
*/
public void execute() throws BuildException
{
logger = new AntLogger(this, msgLevel);
parser = new Parser();
parser.enableLogging(logger);
DirectoryScanner scanner;
String[] list;
String[] dirs;
if (parsertableFilename == null)
throw new BuildException("no parsertable specified", location);
if (baseDir == null)
baseDir = project.resolveFile(".");
File parsertableFile = project.resolveFile(parsertableFilename);
if (!parsertableFile.exists())
{
parsertableFile = fileUtils.resolveFile(baseDir, parsertableFilename);
if (parsertableFile.exists())
{
log("DEPRECATED - the parsertable attribute should be relative to the project\'s");
log(" basedir, not the tasks\'s basedir.");
}
}
if ((inFile != null) && (outFile != null))
{
process(inFile, outFile, parsertableFile);
return;
}
if (destDir == null)
{
String msg = "destdir attributes must be set!";
throw new BuildException(msg);
}
scanner = getDirectoryScanner(baseDir);
log("Transforming into " + destDir, Project.MSG_INFO);
list = scanner.getIncludedFiles();
for (int i = 0; i < list.length; ++i)
process(baseDir, list[i], destDir, parsertableFile);
dirs = scanner.getIncludedDirectories();
for (int j = 0; j < dirs.length; ++j)
{
list = new File(baseDir, dirs[j]).list();
for (int i = 0; i < list.length; ++i)
process(baseDir, list[i], destDir, parsertableFile);
}
}
/**
* Set the base directory.
*
* @param dir Base directory
*/
public void setBasedir(File dir)
{
baseDir = dir;
}
/**
* Set the destination directory into which the result
* files should be copied to
*
* @param dir Destination directory
*/
public void setDestdir(File dir)
{
destDir = dir;
}
/**
* Set the desired file extension to be used for the target
*
* @param name The extension to use
*/
public void setExtension(String name)
{
targetExtension = name;
}
/**
* Sets the parsertable to use for parsing relative to the base directory
* of this task.
*
* @param parsertableFilename File name of the parser table
*/
public void setParserTable(String parsertableFilename)
{
this.parsertableFilename = parsertableFilename;
}
/**
* Sets an output file
*
* @param outFile The output file
*/
public void setOut(File outFile)
{
this.outFile = outFile;
}
/**
* Sets an input xml file to be parsed
*
* @param inFile The input file
*/
public void setIn(File inFile)
{
this.inFile = inFile;
}
/**
* Sets the message level
*
* @param inFile The grammar file
*/
public void setMsglevel(String msgLevel)
{
if (msgLevel.equalsIgnoreCase("debug"))
this.msgLevel = Project.MSG_DEBUG;
else if (msgLevel.equalsIgnoreCase("verbose"))
this.msgLevel = Project.MSG_VERBOSE;
else if (msgLevel.equalsIgnoreCase("info"))
this.msgLevel = Project.MSG_INFO;
else if (msgLevel.equalsIgnoreCase("warn"))
this.msgLevel = Project.MSG_WARN;
else if (msgLevel.equalsIgnoreCase("error"))
this.msgLevel = Project.MSG_ERR;
}
/**
* Sets the encoding for the input file
*
* @param encoding Encoding of the document
*/
public void setEncoding(String encoding)
{
this.encoding = encoding;
}
/**
* Set if the output document should include
* the ignorable tokens
*
* @param ignorableTokens If the output document should include
* the ignorable tokens
*/
public void setIgnorabletokens(boolean ignorableTokens)
{
this.ignorableTokens = ignorableTokens;
}
/**
* Set if the output document should be indented
*
* @param ignorableTokens If the output document should be indented
* the ignorable tokens
*/
public void setIndent(boolean indent)
{
this.indent = indent;
}
/**
* Processes the given input XML file and stores the result
* in the given resultFile.
*
* @param baseDir Base directory
* @param xmlFile File, which should parsed
* @param destDir Destination directory
* @param parsertableFile Parser table file
*
* @throws BuildException
*/
private void process(File baseDir, String xmlFile, File destDir,
File parsertableFile) throws BuildException
{
String fileExt = targetExtension;
File outFile = null;
File inFile = null;
try
{
long parsertableLastModified = parsertableFile.lastModified();
inFile = new File(baseDir, xmlFile);
int dotPos = xmlFile.lastIndexOf('.');
if (dotPos > 0)
outFile = new File(destDir,
xmlFile.substring(0, xmlFile.lastIndexOf('.'))
+ fileExt);
else
outFile = new File(destDir, xmlFile + fileExt);
if (!inFile.exists())
{
log("File " + inFile + " doesn't exists");
return;
}
if (!parsertableFile.exists())
{
log("File " + parsertableFile + " doesn't exists");
return;
}
if ((inFile.lastModified() > outFile.lastModified())
|| (parsertableLastModified > outFile.lastModified()))
{
ensureDirectoryFor(outFile);
log("Parsing files from " + destDir);
ObjectInputStream in = new ObjectInputStream(new FileInputStream(parsertableFile));
ParserTable parsertable = (ParserTable)in.readObject();
in.close();
OutputFormat format = new OutputFormat(Method.XML, "ASCII", indent); // Serialize DOM
if (indent)
{
format.setIndenting(true);
format.setIndent(1);
}
else
format.setPreserveSpace(true);
StringWriter stringOut = new StringWriter(); // Writer will be a String
XMLSerializer serial = new XMLSerializer(stringOut, format);
XMLReader xmlreader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
xmlreader.setContentHandler(new FragmentParser(serial.asContentHandler(), parser,
parsertable, ignorableTokens));
try
{
xmlreader.parse(inFile.toString());
}
catch (SAXParseException se)
{
log("The grammar file is not valid: " + se.getMessage());
return;
}
FileOutputStream outputstream = new FileOutputStream(outFile);
PrintWriter printstream = new PrintWriter(outputstream);
printstream.println(stringOut.toString());
printstream.flush();
printstream.close();
}
}
catch (Exception ex)
{
log("Failed to process " + inFile, Project.MSG_INFO);
if (outFile != null)
outFile.delete();
if (ex instanceof ParserException)
{
log(ex.toString());
throw new BuildException("Parsing faild");
}
else
{
ex.printStackTrace();
throw new BuildException(ex);
}
}
}
/**
* Processes the given input XML file and stores the result
* in the given resultFile.
*
* @param inFile The text file, which should parsed
* @param outFile The output file
* @param parsertableFile Parser table file
*
* @throws BuildException
*/
private void process(File inFile, File outFile,
File parsertableFile) throws BuildException
{
try
{
long parsertableLastModified = parsertableFile.lastModified();
log("In file " + inFile + " time: " + inFile.lastModified(),
Project.MSG_DEBUG);
log("Out file " + outFile + " time: " + outFile.lastModified(),
Project.MSG_DEBUG);
log("Parsertable file " + parsertableFile + " time: "
+ parsertableLastModified, Project.MSG_DEBUG);
if (!inFile.exists())
{
log("File " + inFile + " doesn't exists");
return;
}
if (!parsertableFile.exists())
{
log("File " + parsertableFile + " doesn't exists");
return;
}
if ((inFile.lastModified() > outFile.lastModified())
|| (parsertableLastModified > outFile.lastModified()))
{
ensureDirectoryFor(outFile);
log("Parsing file " + inFile + " to " + outFile, Project.MSG_INFO);
ObjectInputStream in = new ObjectInputStream(new FileInputStream(parsertableFile));
ParserTable parsertable = (ParserTable)in.readObject();
in.close();
OutputFormat format = new OutputFormat(Method.XML, "ASCII", indent); // Serialize DOM
if (indent)
{
format.setIndenting(true);
format.setIndent(1);
}
else
format.setPreserveSpace(true);
StringWriter stringOut = new StringWriter(); // Writer will be a String
XMLSerializer serial = new XMLSerializer(stringOut, format);
XMLReader xmlreader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
xmlreader.setContentHandler(new FragmentParser(serial.asContentHandler(), parser,
parsertable, ignorableTokens));
try
{
xmlreader.parse(inFile.toString());
}
catch (SAXParseException se)
{
log("The grammar file is not valid: " + se.getMessage());
return;
}
FileOutputStream outputstream = new FileOutputStream(outFile);
PrintWriter printstream = new PrintWriter(outputstream);
printstream.println(stringOut.toString());
printstream.flush();
printstream.close();
}
}
catch (Exception ex)
{
log("Failed to process " + inFile, Project.MSG_INFO);
if (outFile != null)
outFile.delete();
if (ex instanceof ParserException)
{
log(ex.toString());
throw new BuildException("Parsing faild");
}
else
{
ex.printStackTrace();
throw new BuildException(ex);
}
}
}
/**
* Ensures the directory for the output
*
* @param targetFile The directory
*
* @throws BuildException
*/
private void ensureDirectoryFor(File targetFile) throws BuildException
{
File directory = new File(targetFile.getParent());
if ((!directory.exists()) && (!directory.mkdirs()))
throw new BuildException("Unable to create directory: "
+ directory.getAbsolutePath());
}
private class FragmentParser implements ContentHandler
{
private String EXTRACT_URI = "http://chaperon.sourceforge.net/schema/textfragment/1.0";
private String EXTRACT_ELEMENT = "textfragment";
private ContentHandler handler = null;
private Parser parser = null;
private ParserTable parsertable = null;
private boolean ignorabletokens = false;
private int extractLevel = 0;
private StringBuffer text;
public FragmentParser(ContentHandler handler, Parser parser, ParserTable parsertable, boolean ignorabletokens)
{
this.handler = handler;
this.parser = parser;
this.parsertable = parsertable;
this.ignorabletokens = ignorabletokens;
}
public void setDocumentLocator(Locator locator)
{
handler.setDocumentLocator(locator);
}
public void startDocument() throws SAXException
{
handler.startDocument();
}
public void endDocument() throws SAXException
{
handler.endDocument();
}
public void startPrefixMapping(String prefix, String uri) throws SAXException
{
if (extractLevel==0)
handler.startPrefixMapping(prefix, uri);
}
public void endPrefixMapping(String prefix) throws SAXException
{
if (extractLevel==0)
handler.endPrefixMapping(prefix);
}
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
{
if (extractLevel > 0)
text.append(ch, start, length);
else
handler.characters(ch, start, length);
}
public void processingInstruction(String target, String data) throws SAXException
{
if (extractLevel==0)
handler.processingInstruction(target, data);
}
public void skippedEntity(String name) throws SAXException
{
if (extractLevel==0)
handler.skippedEntity(name);
}
/**
* Receive notification of the beginning of an element.
*
* @param uri The Namespace URI, or the empty string if the element has no
* Namespace URI or if Namespace
* processing is not being performed.
* @param loc The local name (without prefix), or the empty string if
* Namespace processing is not being performed.
* @param raw The raw XML 1.0 name (with prefix), or the empty string if
* raw names are not available.
* @param a The attributes attached to the element. If there are no
* attributes, it shall be an empty Attributes object.
*/
public void startElement(String uri, String loc, String raw,
Attributes a) throws SAXException
{
if (this.EXTRACT_URI.equals(uri) && this.EXTRACT_ELEMENT.equals(loc))
{
extractLevel++;
text = new StringBuffer();
}
else
if (extractLevel==0)
handler.startElement(uri, loc, raw, a);
}
/**
* Receive notification of the end of an element.
*
* @param uri The Namespace URI, or the empty string if the element has no
* Namespace URI or if Namespace
* processing is not being performed.
* @param loc The local name (without prefix), or the empty string if
* Namespace processing is not being performed.
* @param raw The raw XML 1.0 name (with prefix), or the empty string if
* raw names are not available.
*/
public void endElement(String uri, String loc,
String raw) throws SAXException
{
if (this.EXTRACT_URI.equals(uri) && this.EXTRACT_ELEMENT.equals(loc))
{
extractLevel--;
try
{
EventQueue queue = parser.parse(parsertable, new ByteArrayInputStream(text.toString().getBytes()));
SAXEventAdapter adapter = new SAXEventAdapter(handler, ignorabletokens, true);
queue.fireEvents(adapter);
}
catch (ParserException pe)
{
pe.printStackTrace();
}
text = null;
}
else
if (extractLevel==0)
handler.endElement(uri, loc, raw);
}
/**
* Receive notification of character data.
*
* @param c The characters from the XML document.
* @param start The start position in the array.
* @param len The number of characters to read from the array.
*/
public void characters(char c[], int start, int len) throws SAXException
{
if (extractLevel > 0)
text.append(c, start, len);
else
handler.characters(c, start, len);
}
}
}