/*
* $Id$
*
* Copyright (c) 2004-2005 by the TeXlapse Team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package net.sourceforge.texlipse.builder;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import javax.swing.text.StyledEditorKit.BoldAction;
import net.sourceforge.texlipse.TexlipsePlugin;
import net.sourceforge.texlipse.auxparser.AuxFileParser;
import net.sourceforge.texlipse.model.ReferenceContainer;
import net.sourceforge.texlipse.model.ReferenceEntry;
import net.sourceforge.texlipse.properties.TexlipseProperties;
import net.sourceforge.texlipse.viewer.ViewerManager;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.swt.widgets.Shell;
/**
* Build tex-file(s) using latex or pslatex or pdflatex.
*
* @author Kimmo Karlsson
* @author Tor Arne Vestbø
* @author Boris von Loesch
*/
public class TexBuilder extends AbstractBuilder implements AdaptableBuilder {
private boolean biblatexMode;
private String biblatexBackend;
private ProgramRunner latex;
private ProgramRunner bibtex;
private ProgramRunner makeIndex;
private ProgramRunner makeIndexNomencl;
private String output;
private boolean stopped;
private int alternative;
public TexBuilder(int i, String outputFormat, int alt) {
super(i);
biblatexMode = false;
biblatexBackend = null;
output = outputFormat;
latex = null;
bibtex = null;
makeIndex = null;
alternative = alt;
isValid();
}
/**
* Check if the needed program runners are operational.
* Update runners from registry if necessary.
* @return true, if this builder is ready for operation, false otherwise
*/
public boolean isValid() {
if (latex == null || !latex.isValid()) {
latex = BuilderRegistry.getRunner(TexlipseProperties.INPUT_FORMAT_TEX, output, alternative);
}
if (bibtex == null || !bibtex.isValid()) {
if (!biblatexMode || biblatexBackend == null || "bibtex".equals(biblatexBackend)) {
bibtex = BuilderRegistry.getRunner(TexlipseProperties.INPUT_FORMAT_BIB, TexlipseProperties.OUTPUT_FORMAT_AUX, 0);
}
else if (biblatexMode && "biber".equals(biblatexBackend)) {
bibtex = BuilderRegistry.getRunner(TexlipseProperties.INPUT_FORMAT_BCF, TexlipseProperties.OUTPUT_FORMAT_BBL, 0);
}
}
if (makeIndex == null || !makeIndex.isValid()) {
makeIndex = BuilderRegistry.getRunner(TexlipseProperties.INPUT_FORMAT_IDX, TexlipseProperties.OUTPUT_FORMAT_IDX, 0);
}
if (makeIndexNomencl == null || !makeIndexNomencl.isValid()) {
makeIndexNomencl = BuilderRegistry.getRunner(TexlipseProperties.INPUT_FORMAT_NOMENCL, TexlipseProperties.OUTPUT_FORMAT_NOMENCL, 0);
}
return latex != null && latex.isValid()
&& bibtex != null && bibtex.isValid()
&& makeIndex != null && makeIndex.isValid();
}
/**
* @return output format of the latex processor
*/
public String getOutputFormat() {
return latex.getOutputFormat();
}
/**
* @return sequence
*/
public String getSequence() {
return latex.getProgramName();
}
public void stopRunners() {
latex.stop();
bibtex.stop();
makeIndex.stop();
makeIndexNomencl.stop();
stopped = true;
}
/**
* Opens a messabox where the user is asked wether he wants to continue the build
* @param project
* @return
* @throws CoreException
*/
private boolean askUserForContinue(IProject project) throws CoreException{
Object c = project.getSessionProperty(new QualifiedName(null, "AlwaysContinueBuilding"));
if (c == null) {
// ask the user if he wants to continue
final Shell shell = TexlipsePlugin.getCurrentWorkbenchPage().getActiveEditor().getSite().getShell();
final StringBuffer toggle = new StringBuffer();
final StringBuffer returnCode = new StringBuffer();
shell.getDisplay().syncExec(new Runnable() {
public void run() {
final MessageDialogWithToggle mess = MessageDialogWithToggle.openOkCancelConfirm(
TexlipsePlugin.getCurrentWorkbenchPage().getActiveEditor().getSite().getShell(),
TexlipsePlugin.getResourceString("builderErrorDuringBuildTitle"),
TexlipsePlugin.getResourceString("builderErrorDuringBuild"),
TexlipsePlugin.getResourceString("builderErrorDuringBuildToggle"), false, null, null);
if (mess.getToggleState()) {
toggle.append(true);
}
if (mess.getReturnCode() == MessageDialogWithToggle.CANCEL)
returnCode.append(false);
}
});
if (toggle.length() > 0) {
if (returnCode.length() > 0) {
project.setSessionProperty(new QualifiedName(null, "AlwaysContinueBuilding"), new Boolean(false));
} else {
project.setSessionProperty(new QualifiedName(null, "AlwaysContinueBuilding"), new Boolean(true));
}
}
if (returnCode.length() > 0) {
return false;
}
} else {
//we have a saved state
Boolean b = (Boolean) c;
if (b.booleanValue() == true)
return true;
else
return false;
}
return true;
}
/**
* Calculates the name of the root aux-file to be used by the
* <code>AuxFileParser</code>.
*
* @param project
* @return
*/
private String getAuxFileName(IProject project) {
// evaluate the .aux file
String auxFileName = TexlipseProperties.getProjectProperty(project, TexlipseProperties.MAINFILE_PROPERTY);
//Check for partial build
Object s = TexlipseProperties.getProjectProperty(project, TexlipseProperties.PARTIAL_BUILD_PROPERTY);
if (s != null) {
IFile tmpFile = (IFile)TexlipseProperties.getSessionProperty(project, TexlipseProperties.PARTIAL_BUILD_FILE);
if (tmpFile != null) {
auxFileName = tmpFile.getProjectRelativePath().toPortableString();
}
}
auxFileName = auxFileName.replaceFirst("\\.tex$", "\\.aux");
return auxFileName;
}
/**
* Extracts all labels defined in the aux-file and adds them to the
* label container
*
* @param afp the <code>AuxFileParser</code> used to extract the labels
*/
private void extractLabels(AuxFileParser afp) {
ReferenceContainer labelC = (ReferenceContainer) TexlipseProperties
.getSessionProperty(afp.getProject(),
TexlipseProperties.LABELCONTAINER_PROPERTY);
if (labelC != null) {
// Add temp path to aux-File
String tempPath = TexlipseProperties.getProjectProperty(afp.getProject(),
TexlipseProperties.TEMP_DIR_PROPERTY);
String correctedAuxFileName = tempPath + File.separator
+ afp.getRootAuxFile();
// First remove the labels
labelC.addRefSource(correctedAuxFileName,
new LinkedList<ReferenceEntry>());
// and reorganize
labelC.organize();
// now add them
labelC.updateRefSource(correctedAuxFileName, afp.getLabels());
}
}
/**
* Clears errors and warnings from the problem view. If LaTeX runs more than once, this
* makes sure, the view only shows the messages of the last run, which are still valid.
*
* @param project the project
*/
private void clearMarkers(IProject project) {
try {
project.deleteMarkers(TexlipseBuilder.MARKER_TYPE, false, IResource.DEPTH_INFINITE);
project.deleteMarkers(TexlipseBuilder.LAYOUT_WARNING_TYPE, false, IResource.DEPTH_INFINITE);
} catch (CoreException e) {
}
}
/**
* Run latex and optionally bibtex to produce a dvi file.
* @throws CoreException if the build fails at any point
*/
public void buildResource(IResource resource) throws CoreException {
//boolean error = false;
stopped = false;
// Make sure we close the output document first
// (using DDE on Win32)
if (Platform.getOS().equals(Platform.OS_WIN32)) {
monitor.subTask("Closing output document");
ViewerManager.closeOutputDocument();
monitor.worked(5);
}
IProject project = resource.getProject();
boolean parseAuxFiles = TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.BUILDER_PARSE_AUX_FILES);
String auxFileName = getAuxFileName(project);
IResource auxFile = project.getFile(auxFileName);
List<String> oldCitations = null;
if (!biblatexMode && parseAuxFiles && auxFile.exists()) {
// read all citations from the aux-files and save them for later
AuxFileParser afp = new AuxFileParser(project, auxFileName);
oldCitations = afp.getCitations();
}
monitor.subTask("Building document");
try {
latex.run(resource);
} catch (BuilderCoreException ex) {
//Don't stop here, we will ask the user later
//TODO: Error managment
//error = true;
}
monitor.worked(10);
if (stopped)
return;
String runBib = (String) TexlipseProperties.getSessionProperty(project, TexlipseProperties.SESSION_BIBTEX_RERUN);
Boolean bibChange = (Boolean) TexlipseProperties.getSessionProperty(project, TexlipseProperties.BIBFILES_CHANGED);
IResource runIdx = findIndex(project, resource);
IResource runNomencl = findNomencl(project, resource);
// if bibtex is not used, maybe the references need to be updated in the main document
String rerun = (String) TexlipseProperties.getSessionProperty(resource.getProject(), TexlipseProperties.SESSION_LATEX_RERUN);
if (parseAuxFiles && auxFile.exists()) {
AuxFileParser afp = new AuxFileParser(project, auxFileName);
if (!biblatexMode) {
// check whether a new bibtex run is required
List<String> newCitations = afp.getCitations();
if (!newCitations.equals(oldCitations))
bibChange = new Boolean(true);
}
// add the labels defined in the .aux-file to the label container
extractLabels(afp);
}
// if bibtex is used, the bibliography might be changed
String[] bibs = (String[]) TexlipseProperties.getSessionProperty(project, TexlipseProperties.BIBFILE_PROPERTY);
if (bibs != null && bibs.length > 0 && (runBib != null || bibChange != null)) {
/* if (error) {
if (askUserForContinue(project) == false) {
throw new BuilderCoreException(TexlipsePlugin.stat("Errors during build. See the problems dialog."));
}
}*/
bibtex.run(resource);
if (stopped)
return;
monitor.worked(10);
TexlipseProperties.setSessionProperty(project, TexlipseProperties.SESSION_BIBTEX_RERUN, null);
TexlipseProperties.setSessionProperty(project, TexlipseProperties.BIBFILES_CHANGED, null);
if (runIdx != null) {
makeIndex.run(resource);
if (stopped)
return;
monitor.worked(10);
}
if (runNomencl != null)
{
// Running makeindex to build nomenclature index
// when %input.nlo file is detected
makeIndexNomencl.run(resource);
if (stopped)
return;
monitor.worked(10);
}
try {
latex.run(resource);
} catch (BuilderCoreException ex) {
//if (!error)
// throw ex;
}
if (stopped)
return;
monitor.worked(10);
clearMarkers(project);
try {
latex.run(resource);
} catch (BuilderCoreException ex) {
//if (!error)
// throw ex;
}
if (stopped)
return;
monitor.worked(10);
} else if (rerun != null || runIdx != null || runNomencl != null) {
/* if (error) {
if (askUserForContinue(project) == false) {
throw new BuilderCoreException(TexlipsePlugin.stat("Errors during build. See the problems dialog."));
}
}*/
if (runIdx != null) {
makeIndex.run(resource);
if (stopped)
return;
monitor.worked(10);
}
if (runNomencl != null)
{
// Running makeindex to build nomenclature index
// when %input.nlo file is detected
makeIndexNomencl.run(resource);
if (stopped)
return;
monitor.worked(10);
}
try {
latex.run(resource);
} catch (BuilderCoreException ex) {
//if (!error)
//throw ex;
}
if (stopped)
return;
monitor.worked(10);
TexlipseProperties.setSessionProperty(resource.getProject(), TexlipseProperties.SESSION_LATEX_RERUN, null);
}
}
public void updateBuilder(IProject project) {
// Check if runners need to be updated due to changes in BibTeX / BibLaTeX settings
Boolean newBiblatexMode = (Boolean) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXMODE_PROPERTY);
String newBiblatexBackend = (String) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXBACKEND_PROPERTY);
boolean blModeVal = newBiblatexMode != null;
String blBEVal = newBiblatexBackend != null ? newBiblatexBackend : "";
if (blModeVal != biblatexMode || (biblatexMode && !blBEVal.equals(biblatexBackend))) {
bibtex = null;
// isValid will later re-assign the runners
}
biblatexMode = blModeVal;
biblatexBackend = newBiblatexBackend;
}
/**
* Find a handle to the index file of this project.
* @param project the current project
* @param source buildable resource inside project
* @return handle to index file or null if not found.
* Returns null also if the index file is older than the current output file
*/
private IResource findIndex(IProject project, IResource source) {
IContainer srcDir = TexlipseProperties.getProjectSourceDir(project);
if (srcDir == null) {
srcDir = project;
}
String name = source.getName();
String idxName = name.substring(0, name.length() - source.getFileExtension().length()) + TexlipseProperties.INPUT_FORMAT_IDX;
IResource idxFile = srcDir.findMember(idxName);
if (idxFile == null) {
return null;
}
IResource outFile = TexlipseProperties.getProjectOutputFile(project);
if (outFile.getLocalTimeStamp() > idxFile.getLocalTimeStamp()) {
return null;
}
return idxFile;
}
/**
* Find a handle to the index file of this project.
* @param project the current project
* @param source buildable resource inside project
* @return handle to index file or null if not found.
* Returns null also if the index file is older than the current output file
*/
private IResource findNomencl(IProject project, IResource source) {
IContainer srcDir = TexlipseProperties.getProjectSourceDir(project);
if (srcDir == null) {
srcDir = project;
}
String name = source.getName();
String nomenclName = name.substring(0, name.length() - source.getFileExtension().length()) + TexlipseProperties.INPUT_FORMAT_NOMENCL;
IResource idxFile = srcDir.findMember(nomenclName);
if (idxFile == null) {
return null;
}
IResource outFile = TexlipseProperties.getProjectOutputFile(project);
if (outFile.getLocalTimeStamp() > idxFile.getLocalTimeStamp()) {
return null;
}
return idxFile;
}
}