Пример #1
0
  private BibtexDatabase parseBibtexDatabase(List<String> id, boolean abs) throws IOException {
    if (id.isEmpty()) {
      return null;
    }
    URLConnection conn;
    try {
      conn = new URL(IEEEXploreFetcher.IMPORT_URL).openConnection();
    } catch (MalformedURLException e) {
      e.printStackTrace();
      return null;
    }
    conn.setDoInput(true);
    conn.setDoOutput(true);
    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    conn.setRequestProperty("Referer", searchUrl);
    PrintWriter out = new PrintWriter(conn.getOutputStream());

    String recordIds = "";
    for (String anId : id) {
      recordIds += anId + " ";
    }
    recordIds = recordIds.trim();
    String citation = abs ? "citation-abstract" : "citation-only";

    String content =
        "recordIds="
            + recordIds.replaceAll(" ", "%20")
            + "&fromPageName=&citations-format="
            + citation
            + "&download-format=download-bibtex";
    System.out.println(content);
    out.write(content);
    out.flush();
    out.close();

    BufferedReader bufr = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    StringBuilder sb = new StringBuilder();
    char[] buffer = new char[256];
    while (true) {
      int bytesRead = bufr.read(buffer);
      if (bytesRead == -1) {
        break;
      }
      for (int i = 0; i < bytesRead; i++) {
        sb.append(buffer[i]);
      }
    }
    System.out.println(sb);

    ParserResult results = new BibtexParser(bufr).parse();
    bufr.close();
    return results.getDatabase();
  }
Пример #2
0
 /**
  * Read results from a file instead of an URL. Just for faster debugging.
  *
  * @param f
  * @return
  * @throws IOException
  */
 public String getResultsFromFile(File f) throws IOException {
   InputStream in = new BufferedInputStream(new FileInputStream(f));
   StringBuilder sb = new StringBuilder();
   byte[] buffer = new byte[256];
   while (true) {
     int bytesRead = in.read(buffer);
     if (bytesRead == -1) {
       break;
     }
     for (int i = 0; i < bytesRead; i++) {
       sb.append((char) buffer[i]);
     }
   }
   return sb.toString();
 }
Пример #3
0
  /**
   * Download the URL and return contents as a String.
   *
   * @param source
   * @return
   * @throws IOException
   */
  private String getResults(URL source) throws IOException {

    InputStream in = source.openStream();
    StringBuilder sb = new StringBuilder();
    byte[] buffer = new byte[256];
    while (true) {
      int bytesRead = in.read(buffer);
      if (bytesRead == -1) {
        break;
      }
      for (int i = 0; i < bytesRead; i++) {
        sb.append((char) buffer[i]);
      }
    }
    return sb.toString();
  }
 private static String getKeyString(BibEntry[] bibentries) {
   StringBuilder result = new StringBuilder();
   String citeKey;
   boolean first = true;
   for (BibEntry bes : bibentries) {
     citeKey = bes.getCiteKey();
     // if the key is empty we give a warning and ignore this entry
     if ((citeKey == null) || citeKey.isEmpty()) {
       continue;
     }
     if (first) {
       result.append(citeKey);
       first = false;
     } else {
       result.append(',').append(citeKey);
     }
   }
   return result.toString();
 }
Пример #5
0
  /**
   * This method performs the actual changes.
   *
   * @param panel
   * @param pr
   * @param fileDir The path to the file directory to set, or null if it should not be set.
   */
  private void makeChanges(
      BasePanel panel,
      ParserResult pr,
      boolean upgradePrefs,
      boolean upgradeDatabase,
      String fileDir) {

    if (upgradeDatabase) {
      // Update file links links in the database:
      NamedCompound ce =
          Util.upgradePdfPsToFile(pr.getDatabase(), FileLinksUpgradeWarning.FIELDS_TO_LOOK_FOR);
      panel.undoManager.addEdit(ce);
      panel.markBaseChanged();
    }

    if (fileDir != null) {
      Globals.prefs.put(GUIGlobals.FILE_FIELD + "Directory", fileDir);
    }

    if (upgradePrefs) {
      // Exchange table columns:
      Globals.prefs.putBoolean(JabRefPreferences.PDF_COLUMN, Boolean.FALSE);
      Globals.prefs.putBoolean(JabRefPreferences.FILE_COLUMN, Boolean.TRUE);

      // Modify General fields if necessary:
      // If we don't find the file field, insert it at the bottom of the first tab:
      if (!showsFileInGenFields()) {
        String gfs = Globals.prefs.get(JabRefPreferences.CUSTOM_TAB_FIELDS + "0");
        // System.out.println(gfs);
        StringBuilder sb = new StringBuilder(gfs);
        if (gfs.length() > 0) {
          sb.append(";");
        }
        sb.append(GUIGlobals.FILE_FIELD);
        Globals.prefs.put(JabRefPreferences.CUSTOM_TAB_FIELDS + "0", sb.toString());
        Globals.prefs.updateEntryEditorTabList();
        panel.frame().removeCachedEntryEditors();
      }
      panel.frame().setupAllTables();
    }
  }
Пример #6
0
  public void actionPerformed(ActionEvent event) {
    int selected = editor.getSelectedRow();
    if (selected == -1) return;
    FileListEntry flEntry = editor.getTableModel().getEntry(selected);
    // Check if the current file exists:
    String ln = flEntry.getLink();
    boolean httpLink = ln.toLowerCase().startsWith("http");
    if (httpLink) {
      // TODO: notify that this operation cannot be done on remote links

    }

    // Get an absolute path representation:
    String dir = frame.basePanel().metaData().getFileDirectory(GUIGlobals.FILE_FIELD);
    if ((dir == null) || (dir.trim().length() == 0) || !(new File(dir)).exists()) {
      JOptionPane.showMessageDialog(
          frame,
          Globals.lang("File_directory_is_not_set_or_does_not_exist!"),
          Globals.lang("Move/Rename file"),
          JOptionPane.ERROR_MESSAGE);
      return;
    }
    File file = new File(ln);
    if (!file.isAbsolute()) {
      file = Util.expandFilename(ln, new String[] {dir});
    }
    if ((file != null) && file.exists()) {
      // Ok, we found the file. Now get a new name:
      String extension = null;
      if (flEntry.getType() != null) extension = "." + flEntry.getType().getExtension();

      File newFile = null;
      boolean repeat = true;
      while (repeat) {
        repeat = false;
        String chosenFile;
        if (toFileDir) {
          String suggName = eEditor.getEntry().getCiteKey() + extension;
          CheckBoxMessage cbm =
              new CheckBoxMessage(
                  Globals.lang("Move file to file directory?"),
                  Globals.lang("Rename to '%0'", suggName),
                  Globals.prefs.getBoolean("renameOnMoveFileToFileDir"));
          int answer;
          // Only ask about renaming file if the file doesn't have the proper name already:
          if (!suggName.equals(file.getName()))
            answer =
                JOptionPane.showConfirmDialog(
                    frame, cbm, Globals.lang("Move/Rename file"), JOptionPane.YES_NO_OPTION);
          else
            answer =
                JOptionPane.showConfirmDialog(
                    frame,
                    Globals.lang("Move file to file directory?"),
                    Globals.lang("Move/Rename file"),
                    JOptionPane.YES_NO_OPTION);
          if (answer != JOptionPane.YES_OPTION) return;
          Globals.prefs.putBoolean("renameOnMoveFileToFileDir", cbm.isSelected());
          StringBuilder sb = new StringBuilder(dir);
          if (!dir.endsWith(File.separator)) sb.append(File.separator);
          if (cbm.isSelected()) {
            // Rename:
            sb.append(suggName);
          } else {
            // Do not rename:
            sb.append(file.getName());
          }
          chosenFile = sb.toString();
        } else {
          chosenFile =
              FileDialogs.getNewFile(frame, file, extension, JFileChooser.SAVE_DIALOG, false);
        }
        if (chosenFile == null) {
          return; // cancelled
        }
        newFile = new File(chosenFile);
        // Check if the file already exists:
        if (newFile.exists()
            && (JOptionPane.showConfirmDialog(
                    frame,
                    "'" + newFile.getName() + "' " + Globals.lang("exists. Overwrite file?"),
                    Globals.lang("Move/Rename file"),
                    JOptionPane.OK_CANCEL_OPTION)
                != JOptionPane.OK_OPTION)) {
          if (!toFileDir) repeat = true;
          else return;
        }
      }

      if (!newFile.equals(file)) {
        try {
          boolean success = file.renameTo(newFile);
          if (!success) {
            success = Util.copyFile(file, newFile, true);
          }
          if (success) {
            // Remove the original file:
            file.delete();
            // Relativise path, if possible.
            String canPath = (new File(dir)).getCanonicalPath();
            if (newFile.getCanonicalPath().startsWith(canPath)) {
              if ((newFile.getCanonicalPath().length() > canPath.length())
                  && (newFile.getCanonicalPath().charAt(canPath.length()) == File.separatorChar))
                flEntry.setLink(newFile.getCanonicalPath().substring(1 + canPath.length()));
              else flEntry.setLink(newFile.getCanonicalPath().substring(canPath.length()));

            } else flEntry.setLink(newFile.getCanonicalPath());
            eEditor.updateField(editor);
            JOptionPane.showMessageDialog(
                frame,
                Globals.lang("File moved"),
                Globals.lang("Move/Rename file"),
                JOptionPane.INFORMATION_MESSAGE);
          } else {
            JOptionPane.showMessageDialog(
                frame,
                Globals.lang("Move file failed"),
                Globals.lang("Move/Rename file"),
                JOptionPane.ERROR_MESSAGE);
          }

        } catch (SecurityException ex) {
          ex.printStackTrace();
          JOptionPane.showMessageDialog(
              frame,
              Globals.lang("Could not move file") + ": " + ex.getMessage(),
              Globals.lang("Move/Rename file"),
              JOptionPane.ERROR_MESSAGE);
        } catch (IOException ex) {
          ex.printStackTrace();
          JOptionPane.showMessageDialog(
              frame,
              Globals.lang("Could not move file") + ": " + ex.getMessage(),
              Globals.lang("Move/Rename file"),
              JOptionPane.ERROR_MESSAGE);
        }
      }
    } else {

      // File doesn't exist, so we can't move it.
      JOptionPane.showMessageDialog(
          frame,
          Globals.lang("Could not find file '%0'.", flEntry.getLink()),
          Globals.lang("File not found"),
          JOptionPane.ERROR_MESSAGE);
    }
  }
Пример #7
0
 @Override
 public String getShortDescription() {
   StringBuilder sb = new StringBuilder();
   sb.append("<b>");
   if (Globals.prefs.getBoolean(JabRefPreferences.GROUP_SHOW_DYNAMIC)) {
     sb.append("<i>").append(StringUtil.quoteForHTML(getName())).append("</i>");
   } else {
     sb.append(StringUtil.quoteForHTML(getName()));
   }
   sb.append("</b> - ");
   sb.append(Globals.lang("dynamic group"));
   sb.append("<b>");
   sb.append(searchField);
   sb.append("</b>");
   sb.append(Globals.lang("contains"));
   sb.append(" <b>");
   sb.append(StringUtil.quoteForHTML(searchExpression));
   sb.append("</b>)");
   switch (getHierarchicalContext()) {
     case INCLUDING:
       sb.append(", ").append(Globals.lang("includes subgroups"));
       break;
     case REFINING:
       sb.append(", ").append(Globals.lang("refines supergroup"));
       break;
     default:
       break;
   }
   return sb.toString();
 }
Пример #8
0
  private BibtexEntry parseNextEntry(String allText, int startIndex) {
    BibtexEntry entry = null;

    int index = allText.indexOf("<div class=\"detail", piv);
    int endIndex = allText.indexOf("</div>", index);

    if (index >= 0 && endIndex > 0) {
      endIndex += 6;
      piv = endIndex;
      String text = allText.substring(index, endIndex);

      BibtexEntryType type = null;
      String sourceField = null;

      String typeName = "";
      Matcher typeMatcher = typePattern.matcher(text);
      if (typeMatcher.find()) {
        typeName = typeMatcher.group(1);
        if (typeName.equalsIgnoreCase("IEEE Journals &amp; Magazines")
            || typeName.equalsIgnoreCase("IEEE Early Access Articles")
            || typeName.equalsIgnoreCase("IET Journals &amp; Magazines")
            || typeName.equalsIgnoreCase("AIP Journals &amp; Magazines")
            || typeName.equalsIgnoreCase("AVS Journals &amp; Magazines")
            || typeName.equalsIgnoreCase("IBM Journals &amp; Magazines")
            || typeName.equalsIgnoreCase("TUP Journals &amp; Magazines")
            || typeName.equalsIgnoreCase("BIAI Journals &amp; Magazines")) {
          type = BibtexEntryType.getType("article");
          sourceField = "journal";
        } else if (typeName.equalsIgnoreCase("IEEE Conference Publications")
            || typeName.equalsIgnoreCase("IET Conference Publications")
            || typeName.equalsIgnoreCase("VDE Conference Publications")) {
          type = BibtexEntryType.getType("inproceedings");
          sourceField = "booktitle";
        } else if (typeName.equalsIgnoreCase("IEEE Standards")
            || typeName.equalsIgnoreCase("Standards")) {
          type = BibtexEntryType.getType("standard");
          sourceField = "number";
        } else if (typeName.equalsIgnoreCase("IEEE eLearning Library Courses")) {
          type = BibtexEntryType.getType("Electronic");
          sourceField = "note";
        } else if (typeName.equalsIgnoreCase("Wiley-IEEE Press eBook Chapters")
            || typeName.equalsIgnoreCase("MIT Press eBook Chapters")
            || typeName.equalsIgnoreCase("IEEE USA Books &amp; eBooks")) {
          type = BibtexEntryType.getType("inCollection");
          sourceField = "booktitle";
        }
      }

      if (type == null) {
        type = BibtexEntryType.getType("misc");
        sourceField = "note";
        System.err.println("Type detection failed. Use MISC instead.");
        unparseable++;
        System.err.println(text);
      }

      entry = new BibtexEntry(IdGenerator.next(), type);

      if (typeName.equalsIgnoreCase("IEEE Standards")) {
        entry.setField("organization", "IEEE");
      }

      if (typeName.equalsIgnoreCase("Wiley-IEEE Press eBook Chapters")) {
        entry.setField("publisher", "Wiley-IEEE Press");
      } else if (typeName.equalsIgnoreCase("MIT Press eBook Chapters")) {
        entry.setField("publisher", "MIT Press");
      } else if (typeName.equalsIgnoreCase("IEEE USA Books &amp; eBooks")) {
        entry.setField("publisher", "IEEE USA");
      }

      if (typeName.equalsIgnoreCase("IEEE Early Access Articles")) {
        entry.setField("note", "Early Access");
      }

      Set<String> fields = fieldPatterns.keySet();
      for (String field : fields) {
        Matcher fieldMatcher = Pattern.compile(fieldPatterns.get(field)).matcher(text);
        if (fieldMatcher.find()) {
          entry.setField(field, htmlConverter.format(fieldMatcher.group(1)));
          if (field.equals("title") && fieldMatcher.find()) {
            String sec_title = htmlConverter.format(fieldMatcher.group(1));
            if (entry.getType() == BibtexEntryType.getStandardType("standard")) {
              sec_title = sec_title.replaceAll("IEEE Std ", "");
            }
            entry.setField(sourceField, sec_title);
          }
          if (field.equals("pages") && fieldMatcher.groupCount() == 2) {
            entry.setField(field, fieldMatcher.group(1) + "-" + fieldMatcher.group(2));
          }
        }
      }

      Matcher authorMatcher = authorPattern.matcher(text);
      // System.out.println(text);
      StringBuilder authorNames = new StringBuilder("");
      int authorCount = 0;
      while (authorMatcher.find()) {
        if (authorCount >= 1) {
          authorNames.append(" and ");
        }
        authorNames.append(htmlConverter.format(authorMatcher.group(1)));
        // System.out.println(authorCount + ": " + authorMatcher.group(1));
        authorCount++;
      }
      entry.setField("author", authorNames.toString());
      if (entry.getField("author") == null
          || entry.getField("author").startsWith("a href")
          || entry
              .getField("author")
              .startsWith("Topic(s)")) { // Fix for some documents without authors
        entry.setField("author", "");
      }
      if (entry.getType() == BibtexEntryType.getStandardType("inproceedings")
          && entry.getField("author").equals("")) {
        entry.setType(BibtexEntryType.getStandardType("proceedings"));
      }

      if (includeAbstract) {
        index = text.indexOf("id=\"abstract");
        if (index >= 0) {
          endIndex = text.indexOf("</div>", index) + 6;

          text = text.substring(index, endIndex);
          Matcher absMatcher = absPattern.matcher(text);
          if (absMatcher.find()) {
            // Clean-up abstract
            String abstr = absMatcher.group(1);
            abstr = abstr.replaceAll("<span class='snippet'>([\\w]+)</span>", "$1");

            entry.setField("abstract", htmlConverter.format(abstr));
          }
        }
      }
    }

    if (entry == null) {
      return null;
    } else {
      return cleanup(entry);
    }
  }
Пример #9
0
  /**
   * Formats the content of a field.
   *
   * @param s the content of the field
   * @param fieldName the name of the field - used to trigger different serializations, e.g.,
   *     turning off resolution for some strings
   * @return a formatted string suitable for output
   * @throws IllegalArgumentException if s is not a correct bibtex string, e.g., because of
   *     improperly balanced braces or using # not paired
   */
  public String format(String text, String fieldName) throws IllegalArgumentException {

    if (text == null) {
      return valueDelimiterStartOfValue + "" + valueDelimiterEndOfValue;
    }

    if (Globals.prefs.putBracesAroundCapitals(fieldName) && !BIBTEX_STRING.equals(fieldName)) {
      text = StringUtil.putBracesAroundCapitals(text);
    }

    // normalize newlines
    if (!text.contains(Globals.NEWLINE) && text.contains("\n")) {
      // if we don't have real new lines, but pseudo newlines, we replace them
      // On Win 8.1, this is always true for multiline fields
      text = text.replaceAll("\n", Globals.NEWLINE);
    }

    // If the field is non-standard, we will just append braces,
    // wrap and write.
    boolean resolveStrings = true;
    if (resolveStringsAllFields) {
      // Resolve strings for all fields except some:

      String[] exceptions = doNotResolveStringsFors;
      for (String exception : exceptions) {
        if (exception.equals(fieldName)) {
          resolveStrings = false;
          break;
        }
      }
    } else {
      // Default operation - we only resolve strings for standard fields:
      resolveStrings = BibtexFields.isStandardField(fieldName) || BIBTEX_STRING.equals(fieldName);
    }
    if (!resolveStrings) {
      int numberOfBrackets = 0;
      boolean ok = true;
      for (int i = 0; i < text.length(); i++) {
        char c = text.charAt(i);
        // Util.pr(""+c);
        if (c == '{') {
          numberOfBrackets++;
        }
        if (c == '}') {
          numberOfBrackets--;
        }
        if (numberOfBrackets < 0) {
          ok = false;
          break;
        }
      }
      if (numberOfBrackets > 0) {
        ok = false;
      }
      if (!ok) {
        throw new IllegalArgumentException("Curly braces { and } must be balanced.");
      }

      stringBuilder = new StringBuilder(valueDelimiterStartOfValue + "");
      // No formatting at all for these fields, to allow custom formatting?
      //            if (Globals.prefs.getBoolean("preserveFieldFormatting"))
      //              sb.append(text);
      //            else
      //             currently, we do not do any more wrapping
      // these two are also hard coded in
      // net.sf.jabref.importer.fileformat.FieldContentParser.multiLineFields
      // there, JabRefPreferences.NON_WRAPPABLE_FIELDS are also included
      boolean isAbstract = "abstract".equals(fieldName);
      boolean isReview = "review".equals(fieldName);
      boolean doWrap = !isAbstract || !isReview;
      boolean strangePrefSettings =
          writefieldWrapfield && !Globals.prefs.isNonWrappableField(fieldName);

      if (strangePrefSettings && doWrap) {
        stringBuilder.append(
            parser.format(StringUtil.wrap(text, GUIGlobals.LINE_LENGTH), fieldName));
      } else {
        stringBuilder.append(parser.format(text, fieldName));
      }

      stringBuilder.append(valueDelimiterEndOfValue);

      return stringBuilder.toString();
    }

    stringBuilder = new StringBuilder();
    int pivot = 0;
    int pos1;
    int pos2;
    // Here we assume that the user encloses any bibtex strings in #, e.g.:
    // #jan# - #feb#
    // ...which will be written to the file like this:
    // jan # { - } # feb
    checkBraces(text);

    while (pivot < text.length()) {
      int goFrom = pivot;
      pos1 = pivot;
      while (goFrom == pos1) {
        pos1 = text.indexOf('#', goFrom);
        if (pos1 > 0 && text.charAt(pos1 - 1) == '\\') {
          goFrom = pos1 + 1;
          pos1++;
        } else {
          goFrom = pos1 - 1; // Ends the loop.
        }
      }

      if (pos1 == -1) {
        pos1 = text.length(); // No more occurrences found.
        pos2 = -1;
      } else {
        pos2 = text.indexOf('#', pos1 + 1);
        if (pos2 == -1) {
          if (!neverFailOnHashes) {
            throw new IllegalArgumentException(
                Localization.lang(
                        "The # character is not allowed in BibTeX strings unless escaped as in '\\#'.")
                    + '\n'
                    + Localization.lang(
                        "In JabRef, use pairs of # characters to indicate a string.")
                    + '\n'
                    + Localization.lang(
                        "Note that the entry causing the problem has been selected."));
          } else {
            pos1 = text.length(); // just write out the rest of the text, and throw no exception
          }
        }
      }

      if (pos1 > pivot) {
        writeText(text, pivot, pos1);
      }
      if (pos1 < text.length() && pos2 - 1 > pos1) {
        // We check that the string label is not empty. That means
        // an occurrence of ## will simply be ignored. Should it instead
        // cause an error message?
        writeStringLabel(text, pos1 + 1, pos2, pos1 == pivot, pos2 + 1 == text.length());
      }

      if (pos2 > -1) {
        pivot = pos2 + 1;
      } else {
        pivot = pos1 + 1;
        // if (tell++ > 10) System.exit(0);
      }
    }

    // currently, we do not add newlines and new formatting
    if (writefieldWrapfield && !Globals.prefs.isNonWrappableField(fieldName)) {
      //             introduce a line break to be read at the parser
      return parser.format(
          StringUtil.wrap(stringBuilder.toString(), GUIGlobals.LINE_LENGTH),
          fieldName); // , but that lead to ugly .tex

    } else {
      return parser.format(stringBuilder.toString(), fieldName);
    }
  }
Пример #10
0
 private void putIn(String s) {
   stringBuilder.append(StringUtil.wrap(s, GUIGlobals.LINE_LENGTH));
 }
Пример #11
0
  private void writeText(String text, int start_pos, int end_pos) {
    /*sb.append("{");
    sb.append(text.substring(start_pos, end_pos));
    sb.append("}");*/
    stringBuilder.append(valueDelimiterStartOfValue);
    boolean escape = false;
    boolean inCommandName = false;
    boolean inCommand = false;
    boolean inCommandOption = false;
    int nestedEnvironments = 0;
    StringBuilder commandName = new StringBuilder();
    char c;
    for (int i = start_pos; i < end_pos; i++) {
      c = text.charAt(i);

      // Track whether we are in a LaTeX command of some sort.
      if (Character.isLetter(c) && (escape || inCommandName)) {
        inCommandName = true;
        if (!inCommandOption) {
          commandName.append(c);
        }
      } else if (Character.isWhitespace(c) && (inCommand || inCommandOption)) {
        // System.out.println("whitespace here");
      } else if (inCommandName) {
        // This means the command name is ended.
        // Perhaps the beginning of an argument:
        if (c == '[') {
          inCommandOption = true;
        }
        // Or the end of an argument:
        else if (inCommandOption && c == ']') {
          inCommandOption = false;
        } else if (!inCommandOption && c == '{') {
          // System.out.println("Read command: '"+commandName.toString()+"'");
          inCommandName = false;
          inCommand = true;
        }
        // Or simply the end of this command altogether:
        else {
          // System.out.println("I think I read command: '"+commandName.toString()+"'");

          commandName.delete(0, commandName.length());
          inCommandName = false;
        }
      }
      // If we are in a command body, see if it has ended:
      if (inCommand && c == '}') {
        // System.out.println("nestedEnvironments = " + nestedEnvironments);
        // System.out.println("Done with command: '"+commandName.toString()+"'");
        if (commandName.toString().equals("begin")) {
          nestedEnvironments++;
        }
        if (nestedEnvironments > 0 && commandName.toString().equals("end")) {
          nestedEnvironments--;
        }
        // System.out.println("nestedEnvironments = " + nestedEnvironments);

        commandName.delete(0, commandName.length());
        inCommand = false;
      }

      // We add a backslash before any ampersand characters, with one exception: if
      // we are inside an \\url{...} command, we should write it as it is. Maybe.
      if (c == '&'
          && !escape
          && !(inCommand && commandName.toString().equals("url"))
          && nestedEnvironments == 0) {
        stringBuilder.append("\\&");
      } else {
        stringBuilder.append(c);
      }
      escape = c == '\\';
    }
    stringBuilder.append(valueDelimiterEndOfValue);
  }