/* * $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.texparser; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; import net.sourceforge.texlipse.TexlipsePlugin; import net.sourceforge.texlipse.model.DocumentReference; import net.sourceforge.texlipse.model.OutlineNode; import net.sourceforge.texlipse.model.ParseErrorMessage; import net.sourceforge.texlipse.model.ReferenceEntry; import net.sourceforge.texlipse.model.TexCommandEntry; import net.sourceforge.texlipse.texparser.lexer.LexerException; import net.sourceforge.texlipse.texparser.node.*; import org.eclipse.core.resources.IMarker; /** * Simple parser for LaTeX: does very basic structure checking and * extracts useful data. * * @author Oskar Ojala * @author Boris von Loesch */ public class LatexParser { // These should be allocated between 1000-2000 public static final int TYPE_LABEL = 1000; private static final Pattern PART_RE = Pattern.compile("\\\\part(?:[^a-zA-Z]|$)"); private static final Pattern CHAPTER_RE = Pattern.compile("\\\\chapter(?:[^a-zA-Z]|$)"); private static final Pattern SECTION_RE = Pattern.compile("\\\\section(?:[^a-zA-Z]|$)"); private static final Pattern SSECTION_RE = Pattern.compile("\\\\subsection(?:[^a-zA-Z]|$)"); private static final Pattern SSSECTION_RE = Pattern.compile("\\\\subsubsection(?:[^a-zA-Z]|$)"); private static final Pattern PARAGRAPH_RE = Pattern.compile("\\\\paragraph(?:[^a-zA-Z]|$)"); private static final Pattern LABEL_RE = Pattern.compile("\\\\label(?:[^a-zA-Z]|$)"); /** * Defines a new stack implementation, which is unsynchronized and * tuned for the needs of the parser, making it much faster than * java.util.Stack * * @author Oskar Ojala */ private final static class StackUnsynch<E> { private static final int INITIAL_SIZE = 10; private static final int GROWTH_FACTOR = 2; private int capacity; private int size; private Object[] stack; /** * Creates a new stack. */ public StackUnsynch() { stack = new Object[INITIAL_SIZE]; size = 0; capacity = INITIAL_SIZE; } /** * @return True if the stack is empty, false if it contains items */ public boolean empty() { return (size == 0); } /** * @return The item at the top of the stack */ @SuppressWarnings("unchecked") public E peek() { return (E)(stack[size-1]); } /** * Removes the item at the stop of the stack. * * @return The item at the top of the stack */ @SuppressWarnings("unchecked") public E pop() { size--; E top = (E) stack[size]; stack[size] = null; return top; } /** * Pushes an item to the top of the stack. * * @param item The item to push on the stack */ public void push(final E item) { // what if size would be where to put the next item? if (size >= capacity) { capacity *= GROWTH_FACTOR; Object[] newStack = new Object[capacity]; System.arraycopy(stack, 0, newStack, 0, stack.length); stack = newStack; } stack[size] = item; size++; } /** * Clears the stack; removes all entries. */ public void clear() { for (size--; size >= 0; size--) { stack[size] = null; } size = 0; } } private List<ReferenceEntry> labels; private List<DocumentReference> cites; private List<DocumentReference> refs; private ArrayList<TexCommandEntry> commands; private List<ParseErrorMessage> tasks; private List<String> bibs; private String bibstyle; private List<OutlineNode> inputs; private ArrayList<OutlineNode> outlineTree; private List<ParseErrorMessage> errors; private OutlineNode documentEnv; private boolean biblatexMode; private String biblatexBackend; private boolean localBib; private boolean index; private boolean fatalErrors; /** * Initializes the internal datastructures that are exported after parsing. */ private void initializeDatastructs() { this.labels = new ArrayList<ReferenceEntry>(); this.cites = new ArrayList<DocumentReference>(); this.refs = new ArrayList<DocumentReference>(); this.commands = new ArrayList<TexCommandEntry>(); this.tasks = new ArrayList<ParseErrorMessage>(); this.inputs = new ArrayList<OutlineNode>(2); this.outlineTree = new ArrayList<OutlineNode>(); this.errors = new ArrayList<ParseErrorMessage>(); this.bibs = new ArrayList<String>(); this.biblatexMode = false; this.biblatexBackend = null; this.localBib = false; this.index = false; this.fatalErrors = false; } /** * Adds a node for a document section to the outline tree. * * @param blocks stack with open blocks in outline * @param envBlocks stack with open environments * @param startLine beginning line of this section * @param level node document hierarchy level * @param text node text * @return the document hierarchy level of this node's parent */ private int addSectionNode(final StackUnsynch<OutlineNode> blocks, final StackUnsynch<OutlineNode> envBlocks, final int startLine, final int level, final String text) { int parentLevel = OutlineNode.TYPE_DOCUMENT; OutlineNode on = new OutlineNode(text, level, startLine, null); if (!blocks.empty()) { boolean traversing = true; while (traversing && !blocks.empty()) { OutlineNode prev = blocks.peek(); int prevType = prev.getType(); if (prevType == OutlineNode.TYPE_ENVIRONMENT) { /* An environment is breaking the current section. * Determine next parent which is not an environment, * but an actual document hierarchy node */ OutlineNode topEnv; // First environment on top of previous section OutlineNode parent; // Previous section do { /* Close environments in hierarchy. Blocks will be * synchronized later again using envBlocks. */ topEnv = blocks.pop(); if (!blocks.empty()) { parent = blocks.peek(); prevType = parent.getType(); } else { parent = null; prevType = OutlineNode.TYPE_DOCUMENT; } } while (prevType == OutlineNode.TYPE_ENVIRONMENT); /* If the previous document hierarchy level is deeper than this * one, move the environment one document level up. */ int upperLevel = level - 1; if (prevType > upperLevel) { // Remove environment from current parent parent.deleteChild(topEnv); // Find next higher hierarchy level, if any... int envBegin = topEnv.getBeginLine(); do { // Close blocks parent.setEndLine(envBegin); blocks.pop(); parent = parent.getParent(); } while (parent != null && parent.getType() > upperLevel); // ...and add there, or to tree root if (parent != null) { prevType = parent.getType(); parent.addChild(topEnv); } else { prevType = OutlineNode.TYPE_DOCUMENT; outlineTree.add(topEnv); } topEnv.setParent(parent); } prev = parent; } if (prevType >= OutlineNode.TYPE_DOCUMENT && prevType < level) { parentLevel = prevType; if (prev != null) { prev.addChild(on); } on.setParent(prev); traversing = false; } else { prev.setEndLine(startLine); blocks.pop(); } } } // add directly to tree if no parent was found if (blocks.empty()) { outlineTree.add(on); } blocks.push(on); return parentLevel; } /** * Evaluates package loading options for biblatex and locates the backend * option. * * @param options string with options in format <code>key=value</code>, * or simply <code>key</code>, each separated by commas * @return selected biblatex backend, if it was selected; otherwise null */ private static String findBiblatexBackend(String options) { int beIdx = options.indexOf("backend="); if (beIdx >= 0) { int startIdx = beIdx + 8; // move forward by length of "backend=" int endIdx = options.indexOf(',', startIdx); if (endIdx > startIdx) { return options.substring(startIdx, endIdx).trim(); } else if (endIdx == -1) { return options.substring(startIdx).trim(); } else { return null; } } else { return null; } } /** * Parses a LaTeX document. Uses the given lexer's <code>next()</code> * method to receive tokens that are processed. * * @param lex The lexer to use for extracting the document tokens * @param definedLabels Labels that are defined, used to check for references to * nonexistant labels * @param definedBibs Defined bibliography entries, used to check for references to * nonexistant bibliography entries * @throws LexerException If the given lexer cannot tokenize the document * @throws IOException If the document is unreadable */ public void parse(LatexLexer lex, boolean checkForMissingSections) throws LexerException, IOException { parse(lex, null, checkForMissingSections); } /** * Parses a LaTeX document. Uses the given lexer's <code>next()</code> * method to receive tokens that are processed. * * @param lexer The lexer to use for extracting the document tokens * @param definedLabels Labels that are defined, used to check for references to * nonexistant labels * @param definedBibs Defined bibliography entries, used to check for references to * nonexistant bibliography entries * @param preamble An <code>OutlineNode</code> containing the preamble, null if there is no preamble * @param checkForMissingSections * @throws LexerException If the given lexer cannot tokenize the document * @throws IOException If the document is unreadable */ public void parse(final LatexLexer lexer, final OutlineNode preamble, final boolean checkForMissingSections) throws LexerException, IOException { initializeDatastructs(); StackUnsynch<OutlineNode> blocks = new StackUnsynch<OutlineNode>(); StackUnsynch<OutlineNode> envBlocks = new StackUnsynch<OutlineNode>(); StackUnsynch<Token> braces = new StackUnsynch<Token>(); boolean expectArg = false; boolean expectArg2 = false; Token prevToken = null; String packageOptions = null; TexCommandEntry currentCommand = null; int argCount = 0; int nodeType; HashMap<String, Integer> sectioning = new HashMap<String, Integer>(); if (preamble != null) { outlineTree.add(preamble); blocks.push(preamble); } // newcommand would need to check for the valid format // duplicate labels? // change order of ifs to optimize performance? int accumulatedLength = 0; Token t = lexer.next(); for (; !(t instanceof EOF); t = lexer.next()) { if (expectArg) { if (t instanceof TArgument) { if (prevToken instanceof TClabel) { //this.labels.add(new ReferenceEntry(t.getText())); ReferenceEntry l = new ReferenceEntry(t.getText()); l.setPosition(t.getPos(), t.getText().length()); l.startLine = t.getLine(); this.labels.add(l); OutlineNode on = new OutlineNode(t.getText(), OutlineNode.TYPE_LABEL, t.getLine(), t.getPos(), t.getText().length()); on.setEndLine(t.getLine()); if (!blocks.empty()) { OutlineNode prev = blocks.peek(); prev.addChild(on); on.setParent(prev); } else { outlineTree.add(on); } } else if (prevToken instanceof TCref) { this.refs.add(new DocumentReference(t.getText(), t.getLine(), t.getPos(), t.getText().length())); } else if (prevToken instanceof TCcite) { if (!"*".equals(t.getText())) { String[] cs = t.getText().split(","); for (String c : cs) { //just add all citation and check for errors later, after updating the citation index this.cites.add(new DocumentReference(c.trim(), t.getLine(), t.getPos(), t.getText().length())); } } } else if (prevToken instanceof TCbegin) { // \begin{...} OutlineNode on = new OutlineNode(t.getText(), OutlineNode.TYPE_ENVIRONMENT, t.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length()); if ("document".equals(t.getText())) { if (preamble != null) preamble.setEndLine(t.getLine()); blocks.clear(); documentEnv = on; } else { if (!blocks.empty()) { OutlineNode prev = blocks.peek(); prev.addChild(on); on.setParent(prev); } else { outlineTree.add(on); } blocks.push(on); envBlocks.push(on); } } else if (prevToken instanceof TCend) { // \end{...} int endLine = t.getLine(); OutlineNode prev = null; // check if the document ends if ("document".equals(t.getText())) { documentEnv.setEndLine(endLine + 1); // terminate open blocks here; check for errors while (!blocks.empty()) { prev = blocks.pop(); prev.setEndLine(endLine); if (prev.getType() == OutlineNode.TYPE_ENVIRONMENT) { errors.add(new ParseErrorMessage(prevToken.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length(), "\\end{" + prev.getName() + "} expected, but \\end{document} found; at least one unbalanced begin-end", IMarker.SEVERITY_ERROR)); fatalErrors = true; } } } else { // the "normal" case if (!envBlocks.empty()) { prev = envBlocks.pop(); prev.setEndLine(endLine + 1); if (!prev.getName().equals(t.getText())) { fatalErrors = true; errors.add(new ParseErrorMessage(prev.getBeginLine(), prev.getOffsetOnLine(), prev.getDeclarationLength(), "\\end{" + prev.getName() + "} expected, but \\end{" + t.getText() + "} found; unbalanced begin-end", IMarker.SEVERITY_ERROR)); errors.add(new ParseErrorMessage(prevToken.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length(), "\\end{" + prev.getName() + "} expected, but \\end{" + t.getText() + "} found; unbalanced begin-end", IMarker.SEVERITY_ERROR)); } if (!blocks.empty() && blocks.peek().getType() == OutlineNode.TYPE_ENVIRONMENT) { blocks.pop(); } } else { fatalErrors = true; errors.add(new ParseErrorMessage(prevToken.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length(), "\\end{" + t.getText() + "} found with no preceding \\begin", IMarker.SEVERITY_ERROR)); } } } else if (prevToken instanceof TCpart) { addSectionNode(blocks, envBlocks, prevToken.getLine(), OutlineNode.TYPE_PART, t.getText()); } else if (prevToken instanceof TCchapter) { addSectionNode(blocks, envBlocks, prevToken.getLine(), OutlineNode.TYPE_CHAPTER, t.getText()); } else if (prevToken instanceof TCsection) { addSectionNode(blocks, envBlocks, prevToken.getLine(), OutlineNode.TYPE_SECTION, t.getText()); } else if (prevToken instanceof TCssection) { boolean foundSection = addSectionNode(blocks, envBlocks, prevToken.getLine(), OutlineNode.TYPE_SUBSECTION, t.getText()) >= OutlineNode.TYPE_SECTION; if (!foundSection && checkForMissingSections) { errors.add(new ParseErrorMessage(prevToken.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length(), "Subsection " + prevToken.getText() + " has no preceding section", IMarker.SEVERITY_WARNING)); } } else if (prevToken instanceof TCsssection) { boolean foundSsection = addSectionNode(blocks, envBlocks, prevToken.getLine(), OutlineNode.TYPE_SUBSUBSECTION, t.getText()) >= OutlineNode.TYPE_SUBSECTION; if (!foundSsection && checkForMissingSections) { errors.add(new ParseErrorMessage(prevToken.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length(), "Subsubsection " + prevToken.getText() + " has no preceding subsection", IMarker.SEVERITY_WARNING)); } } else if (prevToken instanceof TCparagraph) { boolean foundSssection = addSectionNode(blocks, envBlocks, prevToken.getLine(), OutlineNode.TYPE_PARAGRAPH, t.getText()) >= OutlineNode.TYPE_SUBSUBSECTION; if (!foundSssection && checkForMissingSections) { errors.add(new ParseErrorMessage(prevToken.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length(), "Paragraph " + prevToken.getText() + " has no preceding subsubsection", IMarker.SEVERITY_WARNING)); } } else if (prevToken instanceof TCbib) { if (biblatexMode) { bibs.add(t.getText().trim()); } else { String[] sBibs = t.getText().split(","); for (String bib : sBibs) { bibs.add(bib.trim()); } int startLine = prevToken.getLine(); while (!blocks.empty()) { OutlineNode prev = blocks.pop(); if (prev.getType() == OutlineNode.TYPE_ENVIRONMENT) { // this is an error... blocks.push(prev); break; } prev.setEndLine(startLine); } } } else if (prevToken instanceof TCbibstyle) { this.bibstyle = t.getText(); int startLine = prevToken.getLine(); while (!blocks.empty()) { OutlineNode prev = blocks.pop(); if (prev.getType() == OutlineNode.TYPE_ENVIRONMENT) { // this is an error... blocks.push(prev); break; } prev.setEndLine(startLine); } } else if (prevToken instanceof TCinput || prevToken instanceof TCinclude) { //inputs.add(t.getText()); if (!blocks.empty()) { OutlineNode prev = blocks.peek(); OutlineNode on = new OutlineNode(t.getText(), OutlineNode.TYPE_INPUT, t.getLine(), prev); on.setEndLine(t.getLine()); prev.addChild(on); inputs.add(on); } else { OutlineNode on = new OutlineNode(t.getText(), OutlineNode.TYPE_INPUT, t.getLine(), null); on.setEndLine(t.getLine()); outlineTree.add(on); inputs.add(on); } } else if (prevToken instanceof TCnew) { //currentCommand = new CommandEntry(t.getText().substring(1)); currentCommand = new TexCommandEntry(t.getText().substring(1), "", 0); currentCommand.startLine = t.getLine(); lexer.registerCommand(currentCommand.key); expectArg2 = true; } else if (prevToken instanceof TCpackage) { if (t.getText().equals("biblatex")) { biblatexMode = true; if (packageOptions != null) { biblatexBackend = findBiblatexBackend(packageOptions); // reset packageOptions = null; } } } // reset state to normal scanning accumulatedLength = 0; prevToken = null; expectArg = false; } else if ((t instanceof TCword) && (prevToken instanceof TCnew)) { // this handles the \newcommand\comx{...} -format //currentCommand = new CommandEntry(t.getText().substring(1)); currentCommand = new TexCommandEntry(t.getText().substring(1), "", 0); currentCommand.startLine = t.getLine(); lexer.registerCommand(currentCommand.key); expectArg2 = true; accumulatedLength = 0; prevToken = null; expectArg = false; } else if (t instanceof TOptargument) { if (prevToken instanceof TCpackage) { packageOptions = t.getText(); } accumulatedLength += t.getText().length(); } else if (!(t instanceof TWhitespace) && !(t instanceof TStar) && !(t instanceof TCommentline) && !(t instanceof TTaskcomment)) { // if we didn't get the mandatory argument we were expecting... //fatalErrors = true; errors.add(new ParseErrorMessage(prevToken.getLine(), prevToken.getPos(), prevToken.getText().length() + accumulatedLength + t.getText().length(), "No argument following " + prevToken.getText(), IMarker.SEVERITY_WARNING)); accumulatedLength = 0; prevToken = null; expectArg = false; } else { accumulatedLength += t.getText().length(); } } else if (expectArg2) { // we are capturing the second argument of a command with two arguments // the only one of those that interests us is newcommand if (t instanceof TArgument) { currentCommand.info = t.getText(); commands.add(currentCommand); if (PART_RE.matcher(currentCommand.info).find()) sectioning.put("\\" + currentCommand.key, OutlineNode.TYPE_PART); //else if (currentCommand.info.indexOf("\\chapter") != -1) else if (CHAPTER_RE.matcher(currentCommand.info).find()) sectioning.put("\\" + currentCommand.key, OutlineNode.TYPE_CHAPTER); //else if (currentCommand.info.indexOf("\\section") != -1) else if (SECTION_RE.matcher(currentCommand.info).find()) sectioning.put("\\" + currentCommand.key, OutlineNode.TYPE_SECTION); //else if (currentCommand.info.indexOf("\\subsection") != -1) else if (SSECTION_RE.matcher(currentCommand.info).find()) sectioning.put("\\" + currentCommand.key, OutlineNode.TYPE_SUBSECTION); //else if (currentCommand.info.indexOf("\\subsubsection") != -1) else if (SSSECTION_RE.matcher(currentCommand.info).find()) sectioning.put("\\" + currentCommand.key, OutlineNode.TYPE_SUBSUBSECTION); //else if (currentCommand.info.indexOf("\\paragraph") != -1) else if (PARAGRAPH_RE.matcher(currentCommand.info).find()) sectioning.put("\\" + currentCommand.key, OutlineNode.TYPE_PARAGRAPH); //else if (currentCommand.info.indexOf("\\label") != -1) else if (LABEL_RE.matcher(currentCommand.info).find()) sectioning.put("\\" + currentCommand.key, LatexParser.TYPE_LABEL); argCount = 0; expectArg2 = false; } else if (t instanceof TOptargument) { if (argCount == 0) { try { currentCommand.arguments = Integer.parseInt(t.getText()); } catch (NumberFormatException nfe) { errors.add(new ParseErrorMessage(prevToken.getLine(), t.getPos(), t.getText().length(), "The first optional argument of newcommand must only contain the number of arguments", IMarker.SEVERITY_ERROR)); expectArg2 = false; } } argCount++; } else if (!(t instanceof TWhitespace) && !(t instanceof TCommentline) && !(t instanceof TTaskcomment)) { // if we didn't get the mandatory argument we were expecting... errors.add(new ParseErrorMessage(t.getLine(), t.getPos(), t.getText().length(), "No 2nd argument following newcommand", IMarker.SEVERITY_WARNING)); argCount = 0; expectArg2 = false; } } else { if (t instanceof TClabel || t instanceof TCref || t instanceof TCcite || t instanceof TCbib || t instanceof TCbibstyle || t instanceof TCbegin || t instanceof TCend || t instanceof TCinput || t instanceof TCinclude || t instanceof TCpart || t instanceof TCchapter || t instanceof TCsection || t instanceof TCssection || t instanceof TCsssection || t instanceof TCparagraph || t instanceof TCpackage || t instanceof TCnew) { prevToken = t; expectArg = true; } else if (t instanceof TCword) { // macros (\newcommand) show up as TCword when used, so we need // to check (for each word!) whether it happens to be a command if (sectioning.containsKey(t.getText())) { nodeType = sectioning.get(t.getText()); switch (nodeType) { case OutlineNode.TYPE_PART: prevToken = new TCpart(t.getLine(), t.getPos()); break; case OutlineNode.TYPE_CHAPTER: prevToken = new TCchapter(t.getLine(), t.getPos()); break; case OutlineNode.TYPE_SECTION: prevToken = new TCsection(t.getLine(), t.getPos()); break; case OutlineNode.TYPE_SUBSECTION: prevToken = new TCssection(t.getLine(), t.getPos()); break; case OutlineNode.TYPE_SUBSUBSECTION: prevToken = new TCsssection(t.getLine(), t.getPos()); break; case OutlineNode.TYPE_PARAGRAPH: prevToken = new TCparagraph(t.getLine(), t.getPos()); break; case LatexParser.TYPE_LABEL: prevToken = new TClabel(t.getLine(), t.getPos()); break; default: break; } expectArg = true; } } else if (t instanceof TCpindex) { this.index = true; } else if (t instanceof TCpbib) { int startLine = t.getLine(); while (!blocks.empty()) { OutlineNode prev = blocks.pop(); if (prev.getType() == OutlineNode.TYPE_ENVIRONMENT) { // this is an error... blocks.push(prev); break; } prev.setEndLine(startLine); } this.localBib = true; } else if (t instanceof TTaskcomment) { int severity = IMarker.PRIORITY_HIGH; int start = t.getText().indexOf("FIXME"); if (start == -1) { severity = IMarker.PRIORITY_NORMAL; start = t.getText().indexOf("TODO"); if (start == -1) { start = t.getText().indexOf("XXX"); } } String taskText = t.getText().substring(start).trim(); tasks.add(new ParseErrorMessage(t.getLine(), t.getPos(), taskText.length(), taskText, severity)); } else if (t instanceof TVtext) { // Fold OutlineNode on = new OutlineNode(t.getText(), OutlineNode.TYPE_ENVIRONMENT, t.getLine(), t.getPos(), t.getText().length()); // TODO uses memory, but doesn't require much code... String[] lines = t.getText().split("\\r\\n|\\n|\\r"); on.setEndLine(t.getLine() + lines.length); if (!blocks.empty()) { OutlineNode prev = blocks.peek(); prev.addChild(on); on.setParent(prev); } else { outlineTree.add(on); } } } if (t instanceof TLBrace) { braces.push(t); } else if (t instanceof TRBrace) { if (braces.empty()) { //There is an opening brace missing errors.add(new ParseErrorMessage(t.getLine(), t.getPos()-1, 1, TexlipsePlugin.getResourceString("parseErrorMissingLBrace"), IMarker.SEVERITY_ERROR)); } else { braces.pop(); } } } //Check for missing closing braces while (!braces.empty()) { Token mt = (Token) braces.pop(); errors.add(new ParseErrorMessage(mt.getLine(), mt.getPos() - 1, 1, TexlipsePlugin.getResourceString("parseErrorMissingRBrace"), IMarker.SEVERITY_ERROR)); } int endLine = t.getLine() + 1; //endline is exclusive while (!blocks.empty()) { OutlineNode prev = blocks.pop(); prev.setEndLine(endLine); if (prev.getType() == OutlineNode.TYPE_ENVIRONMENT) { envBlocks.pop(); } } while (!envBlocks.empty()) { OutlineNode prev = envBlocks.pop(); prev.setEndLine(endLine); fatalErrors = true; errors.add(new ParseErrorMessage(prev.getBeginLine(), 0, prev.getName().length(), "\\begin{" + prev.getName() + "} does not have matching end; at least one unbalanced begin-end", IMarker.SEVERITY_ERROR)); } } /** * @return The labels defined in this document */ public List<ReferenceEntry> getLabels() { return this.labels; } /** * @return The BibTeX citations */ public List<DocumentReference> getCites() { return this.cites; } /** * @return The refencing commands */ public List<DocumentReference> getRefs() { return this.refs; } /** * @return The bibliography files to use. */ public String[] getBibs() { return this.bibs.toArray(new String[0]); } /** * @return The bibliography style. */ public String getBibstyle() { return bibstyle; } /** * @return The input commands in this document */ public List<OutlineNode> getInputs() { return this.inputs; } /** * @return The outline tree of the document (OutlineNode objects). */ public ArrayList<OutlineNode> getOutlineTree() { return this.outlineTree; } /** * @return The list of errors (ParseErrorMessage objects) in the document */ public List<ParseErrorMessage> getErrors() { return this.errors; } /** * @return Whether biblatex mode is activated */ public boolean isBiblatexMode() { return biblatexMode; } /** * @return The selected biblatex backend */ public String getBiblatexBackend() { return biblatexBackend; } /** * @return Whether the parsed file contains a bibliography print command. * This is only relevant if biblatex mode is enabled. */ public boolean isLocalBib() { return localBib; } /** * @return Returns whether makeindex is to be used or not */ public boolean isIndex() { return index; } /** * @return Returns the documentEnv. */ public OutlineNode getDocumentEnv() { return documentEnv; } /** * @return Returns whether there are fatal errors in the document */ public boolean isFatalErrors() { return fatalErrors; } /** * @return Returns the commands. */ public ArrayList<TexCommandEntry> getCommands() { return commands; } /** * @return Returns the tasks. */ public List<ParseErrorMessage> getTasks() { return tasks; } }