예제 #1
0
  private void parseField(BibEntry entry) throws IOException {
    String key = parseTextToken().toLowerCase();

    skipWhitespace();
    consume('=');
    String content = parseFieldContent(key);
    if (!content.isEmpty()) {
      if (entry.hasField(key)) {
        // The following hack enables the parser to deal with multiple
        // author or
        // editor lines, stringing them together instead of getting just
        // one of them.
        // Multiple author or editor lines are not allowed by the bibtex
        // format, but
        // at least one online database exports bibtex like that, making
        // it inconvenient
        // for users if JabRef didn't accept it.
        if (InternalBibtexFields.getFieldExtras(key).contains(FieldProperties.PERSON_NAMES)) {
          entry.setField(key, entry.getFieldOptional(key).get() + " and " + content);
        } else if (FieldName.KEYWORDS.equals(key)) {
          // multiple keywords fields should be combined to one
          entry.addKeyword(content, Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR));
        }
      } else {
        entry.setField(key, content);
      }
    }
  }
  @Test
  public void importConvertsToCorrectBibType() throws IOException {
    String bsInput =
        "--AU-- Baklouti, F.\n"
            + "--YP-- 1999\n"
            + "--KW-- Cells; Rna; Isoforms\n"
            + "--TI-- Blood\n"
            + "--RT-- "
            + biblioscapeType
            + "\n"
            + "------";

    List<BibEntry> bibEntries =
        bsImporter
            .importDatabase(new BufferedReader(new StringReader(bsInput)))
            .getDatabase()
            .getEntries();

    BibEntry entry = new BibEntry();
    entry.setField("author", "Baklouti, F.");
    entry.setField("keywords", "Cells; Rna; Isoforms");
    entry.setField("title", "Blood");
    entry.setField("year", "1999");
    entry.setType(expectedBibType);

    Assert.assertEquals(Collections.singletonList(entry), bibEntries);
  }
예제 #3
0
  @Test
  public void testSerialization() throws IOException {
    StringWriter stringWriter = new StringWriter();

    BibEntry entry = new BibEntry("1234", "article");
    // set a required field
    entry.setField("author", "Foo Bar");
    entry.setField("journal", "International Journal of Something");
    // set an optional field
    entry.setField("number", "1");
    entry.setField("note", "some note");

    writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX);

    String actual = stringWriter.toString();

    // @formatter:off
    String expected =
        OS.NEWLINE
            + "@Article{,"
            + OS.NEWLINE
            + "  author  = {Foo Bar},"
            + OS.NEWLINE
            + "  journal = {International Journal of Something},"
            + OS.NEWLINE
            + "  number  = {1},"
            + OS.NEWLINE
            + "  note    = {some note},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE;
    // @formatter:on

    assertEquals(expected, actual);
  }
예제 #4
0
  @Test
  public void isMatchedForNormalAndFieldBasedSearchMixed() {
    BibEntry entry = new BibEntry();
    entry.setType(BibtexEntryTypes.ARTICLE);
    entry.setField("author", "asdf");
    entry.setField("abstract", "text");

    assertTrue(new SearchQuery("text AND author=asdf", true, true).isMatch(entry));
  }
예제 #5
0
  @Test
  public void testSearchAllFieldsNotForSpecificField() {
    BibEntry e = new BibEntry(BibtexEntryTypes.INPROCEEDINGS.getName());
    e.setField("title", "Fruity features");
    e.setField("keywords", "banana, pineapple, orange");

    SearchQuery searchQuery = new SearchQuery("anyfield=fruit and keywords!=banana", false, false);
    assertFalse(searchQuery.isMatch(e));
  }
예제 #6
0
  @Override
  public List<FieldChange> cleanup(BibEntry entry) {

    ArrayList<FieldChange> changes = new ArrayList<>();

    // First check if the Doi Field is empty
    if (entry.getField("doi") != null) {
      String doiFieldValue = entry.getField("doi");

      Optional<DOI> doi = DOI.build(doiFieldValue);

      if (doi.isPresent()) {
        String newValue = doi.get().getDOI();
        if (!doiFieldValue.equals(newValue)) {
          entry.setField("doi", newValue);

          FieldChange change = new FieldChange(entry, "doi", doiFieldValue, newValue);
          changes.add(change);
        }

        // Doi field seems to contain Doi -> cleanup note, url, ee field
        for (String field : fields) {
          DOI.build(entry.getField((field)))
              .ifPresent(unused -> removeFieldValue(entry, field, changes));
        }
      }
    } else {
      // As the Doi field is empty we now check if note, url, or ee field contains a Doi
      for (String field : fields) {
        Optional<DOI> doi = DOI.build(entry.getField(field));

        if (doi.isPresent()) {
          // update Doi
          String oldValue = entry.getField("doi");
          String newValue = doi.get().getDOI();

          entry.setField("doi", newValue);

          FieldChange change = new FieldChange(entry, "doi", oldValue, newValue);
          changes.add(change);

          removeFieldValue(entry, field, changes);
        }
      }
    }

    return changes;
  }
예제 #7
0
  /**
   * Unabbreviate the journal name of the given entry.
   *
   * @param entry The entry to be treated.
   * @param fieldName The field name (e.g. "journal")
   * @param ce If the entry is changed, add an edit to this compound.
   * @return true if the entry was changed, false otherwise.
   */
  public boolean unabbreviate(
      BibDatabase database, BibEntry entry, String fieldName, CompoundEdit ce) {
    if (!entry.hasField(fieldName)) {
      return false;
    }
    String text = entry.getFieldOptional(fieldName).get();
    String origText = text;
    if (database != null) {
      text = database.resolveForStrings(text);
    }

    if (!journalAbbreviationRepository.isKnownName(text)) {
      return false; // cannot do anything if it is not known
    }

    if (!journalAbbreviationRepository.isAbbreviatedName(text)) {
      return false; // cannot unabbreviate unabbreviated name.
    }

    Abbreviation abbreviation =
        journalAbbreviationRepository.getAbbreviation(text).get(); // must be here
    String newText = abbreviation.getName();
    entry.setField(fieldName, newText);
    ce.addEdit(new UndoableFieldChange(entry, fieldName, origText, newText));
    return true;
  }
예제 #8
0
  @Test
  // For https://github.com/JabRef/jabref/issues/1873
  public void containsOnlyMatchesCompletePhraseWithSlash() throws Exception {
    entry.setField(FieldName.GROUPS, "myExplicitGroup/b");

    assertFalse(group.contains(entry));
  }
예제 #9
0
  @Test
  public void roundtripWithUserCommentAndEntryChange() throws Exception {
    Path testBibtexFile = Paths.get("src/test/resources/testbib/bibWithUserComments.bib");
    Charset encoding = StandardCharsets.UTF_8;
    ParserResult result =
        new BibtexParser(importFormatPreferences)
            .parse(Importer.getReader(testBibtexFile, encoding));

    BibEntry entry = result.getDatabase().getEntryByKey("1137631").get();
    entry.setField("author", "Mr. Author");

    SavePreferences preferences =
        new SavePreferences().withEncoding(encoding).withSaveInOriginalOrder(true);
    BibDatabaseContext context =
        new BibDatabaseContext(
            result.getDatabase(), result.getMetaData(), new Defaults(BibDatabaseMode.BIBTEX));

    StringSaveSession session =
        databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries(), preferences);

    try (Scanner scanner =
        new Scanner(
            Paths.get("src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib"),
            encoding.name())) {
      assertEquals(scanner.useDelimiter("\\A").next(), session.getStringValue());
    }
  }
예제 #10
0
 @Test
 public void testGrammarSearchFullEntry() {
   BibEntry entry = new BibEntry();
   entry.setField(FieldName.TITLE, "systematic review");
   SearchQuery searchQuery = new SearchQuery("title=\"systematic review\"", false, false);
   assertTrue(searchQuery.isMatch(entry));
 }
예제 #11
0
  @Test
  public void addDuplicateGroupDoesNotChangeGroupsField() throws Exception {
    entry.setField(FieldName.GROUPS, "myExplicitGroup");
    group.add(entry);

    assertEquals(Optional.of("myExplicitGroup"), entry.getField(FieldName.GROUPS));
  }
예제 #12
0
  @Test
  public void reformatEntryIfAskedToDoSo() throws Exception {
    BibEntry entry = new BibEntry();
    entry.setType(BibtexEntryTypes.ARTICLE);
    entry.setField("author", "Mr. author");
    entry.setParsedSerialization("wrong serialization");
    entry.setChanged(false);
    database.insertEntry(entry);

    SavePreferences preferences = new SavePreferences().withReformatFile(true);
    StringSaveSession session =
        databaseWriter.savePartOfDatabase(
            bibtexContext, Collections.singletonList(entry), preferences);

    assertEquals(
        OS.NEWLINE
            + "@Article{,"
            + OS.NEWLINE
            + "  author = {Mr. author},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE
            + OS.NEWLINE
            + "@Comment{jabref-meta: databaseType:bibtex;}"
            + OS.NEWLINE,
        session.getStringValue());
  }
예제 #13
0
  @Test
  public void addSingleGroupToNonemptyBibEntryAppendsToGroupsField() {
    entry.setField(FieldName.GROUPS, "some thing");
    group.add(entry);

    assertEquals(Optional.of("some thing, myExplicitGroup"), entry.getField(FieldName.GROUPS));
  }
예제 #14
0
  @Test
  public void testSearchMatchesSingleKeyword() {
    BibEntry e = new BibEntry(BibtexEntryTypes.INPROCEEDINGS.getName());
    e.setField("keywords", "banana, pineapple, orange");

    SearchQuery searchQuery = new SearchQuery("anykeyword==pineapple", false, false);
    assertTrue(searchQuery.isMatch(e));
  }
예제 #15
0
  @Test
  // For https://github.com/JabRef/jabref/issues/2334
  public void removeDoesNotChangeFieldIfContainsNameAsWord() throws Exception {
    entry.setField(FieldName.GROUPS, "myExplicitGroup alternative");
    group.remove(entry);

    assertEquals(Optional.of("myExplicitGroup alternative"), entry.getField(FieldName.GROUPS));
  }
예제 #16
0
  @Test
  public void testSearchingForOpenBraketInBooktitle() {
    BibEntry e = new BibEntry(BibtexEntryTypes.INPROCEEDINGS.getName());
    e.setField(FieldName.BOOKTITLE, "Super Conference (SC)");

    SearchQuery searchQuery = new SearchQuery("booktitle=\"(\"", false, false);
    assertTrue(searchQuery.isMatch(e));
  }
예제 #17
0
  private static BibEntry downloadEntryBibTeX(String id, boolean downloadAbstract) {
    try {
      URL url =
          new URL(
              ACMPortalFetcher.START_URL
                  + ACMPortalFetcher.BIBTEX_URL
                  + id
                  + ACMPortalFetcher.BIBTEX_URL_END);
      URLConnection connection = url.openConnection();

      // set user-agent to avoid being blocked as a crawler
      connection.addRequestProperty(
          "User-Agent", "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0");
      Collection<BibEntry> items = null;
      try (BufferedReader in =
          new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
        items = BibtexParser.parse(in).getDatabase().getEntries();
      } catch (IOException e) {
        LOGGER.info("Download of BibTeX information from ACM Portal failed.", e);
      }
      if ((items == null) || items.isEmpty()) {
        return null;
      }
      BibEntry entry = items.iterator().next();
      Thread.sleep(
          ACMPortalFetcher.WAIT_TIME); // wait between requests or you will be blocked by ACM

      // get abstract
      if (downloadAbstract) {
        url = new URL(ACMPortalFetcher.START_URL + ACMPortalFetcher.ABSTRACT_URL + id);
        String page = Util.getResults(url);
        Matcher absM = ACMPortalFetcher.ABSTRACT_PATTERN.matcher(page);
        if (absM.find()) {
          entry.setField("abstract", absM.group(1).trim());
        }
        Thread.sleep(
            ACMPortalFetcher.WAIT_TIME); // wait between requests or you will be blocked by ACM
      }

      return entry;
    } catch (NoSuchElementException e) {
      LOGGER.info(
          "Bad Bibtex record read at: "
              + ACMPortalFetcher.BIBTEX_URL
              + id
              + ACMPortalFetcher.BIBTEX_URL_END,
          e);
      return null;
    } catch (MalformedURLException e) {
      LOGGER.info("Malformed URL.", e);
      return null;
    } catch (IOException e) {
      LOGGER.info("Cannot connect.", e);
      return null;
    } catch (InterruptedException ignored) {
      return null;
    }
  }
예제 #18
0
  @Test
  public void notFoundByDOI() throws IOException {
    // CI server is unreliable
    Assume.assumeFalse(DevEnvironment.isCIServer());

    entry.setField("doi", "10.1021/bk-2006-WWW.ch014");

    Assert.assertEquals(Optional.empty(), finder.findFullText(entry));
  }
예제 #19
0
  @Test
  public void testIsMatch() {
    BibEntry entry = new BibEntry();
    entry.setType(BibtexEntryTypes.ARTICLE);
    entry.setField("author", "asdf");

    assertFalse(new SearchQuery("qwer", true, true).isMatch(entry));
    assertTrue(new SearchQuery("asdf", true, true).isMatch(entry));
    assertTrue(new SearchQuery("author=asdf", true, true).isMatch(entry));
  }
예제 #20
0
  @Test
  public void findByDOI() throws IOException {
    // CI server is unreliable
    Assume.assumeFalse(DevEnvironment.isCIServer());

    entry.setField("doi", "10.1021/bk-2006-STYG.ch014");

    Assert.assertEquals(
        Optional.of(new URL("http://pubs.acs.org/doi/pdf/10.1021/bk-2006-STYG.ch014")),
        finder.findFullText(entry));
  }
예제 #21
0
  @Override
  public void getEntries(Map<String, Boolean> selection, ImportInspector inspector) {
    for (Map.Entry<String, Boolean> selentry : selection.entrySet()) {
      if (!shouldContinue) {
        break;
      }
      boolean sel = selentry.getValue();
      if (sel) {
        BibEntry entry = downloadEntryBibTeX(selentry.getKey(), fetchAbstract);
        if (entry != null) {
          // Convert from HTML and optionally add curly brackets around key words to keep the case
          entry
              .getFieldOptional("title")
              .ifPresent(
                  title -> {
                    title = title.replaceAll("\\\\&", "&").replaceAll("\\\\#", "#");
                    title = convertHTMLChars(title);

                    // Unit formatting
                    if (Globals.prefs.getBoolean(JabRefPreferences.USE_UNIT_FORMATTER_ON_SEARCH)) {
                      title = unitFormatter.format(title);
                    }

                    // Case keeping
                    if (Globals.prefs.getBoolean(JabRefPreferences.USE_CASE_KEEPER_ON_SEARCH)) {
                      title = caseKeeper.format(title);
                    }
                    entry.setField("title", title);
                  });

          entry
              .getFieldOptional("abstract")
              .ifPresent(
                  abstr -> {
                    entry.setField("abstract", convertHTMLChars(abstr));
                  });
          inspector.addEntry(entry);
        }
      }
    }
  }
예제 #22
0
  @Test
  public void roundTripWithCamelCasingInTheOriginalEntryAndResultInLowerCase() throws IOException {
    // @formatter:off
    String bibtexEntry =
        OS.NEWLINE
            + "@Article{test,"
            + OS.NEWLINE
            + "  Author                   = {Foo Bar},"
            + OS.NEWLINE
            + "  Journal                  = {International Journal of Something},"
            + OS.NEWLINE
            + "  Note                     = {some note},"
            + OS.NEWLINE
            + "  Number                   = {1},"
            + OS.NEWLINE
            + "  HowPublished             = {asdf},"
            + OS.NEWLINE
            + "}";
    // @formatter:on

    // read in bibtex string
    ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry));
    Collection<BibEntry> entries = result.getDatabase().getEntries();
    BibEntry entry = entries.iterator().next();

    // modify entry
    entry.setField("author", "BlaBla");

    // write out bibtex string
    StringWriter stringWriter = new StringWriter();
    writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX);
    String actual = stringWriter.toString();

    // @formatter:off
    String expected =
        OS.NEWLINE
            + "@Article{test,"
            + OS.NEWLINE
            + "  author       = {BlaBla},"
            + OS.NEWLINE
            + "  journal      = {International Journal of Something},"
            + OS.NEWLINE
            + "  number       = {1},"
            + OS.NEWLINE
            + "  note         = {some note},"
            + OS.NEWLINE
            + "  howpublished = {asdf},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE;
    // @formatter:on
    assertEquals(expected, actual);
  }
예제 #23
0
  @Test
  public void roundTripWithPrecedingCommentAndModificationTest() throws IOException {
    // @formatter:off
    String bibtexEntry =
        "% Some random comment that should stay here"
            + OS.NEWLINE
            + "@Article{test,"
            + OS.NEWLINE
            + "  Author                   = {Foo Bar},"
            + OS.NEWLINE
            + "  Journal                  = {International Journal of Something},"
            + OS.NEWLINE
            + "  Note                     = {some note},"
            + OS.NEWLINE
            + "  Number                   = {1}"
            + OS.NEWLINE
            + "}";
    // @formatter:on

    // read in bibtex string
    ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry));
    Collection<BibEntry> entries = result.getDatabase().getEntries();
    BibEntry entry = entries.iterator().next();

    // change the entry
    entry.setField("author", "John Doe");

    // write out bibtex string
    StringWriter stringWriter = new StringWriter();
    writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX);
    String actual = stringWriter.toString();
    // @formatter:off
    String expected =
        "% Some random comment that should stay here"
            + OS.NEWLINE
            + OS.NEWLINE
            + "@Article{test,"
            + OS.NEWLINE
            + "  author  = {John Doe},"
            + OS.NEWLINE
            + "  journal = {International Journal of Something},"
            + OS.NEWLINE
            + "  number  = {1},"
            + OS.NEWLINE
            + "  note    = {some note},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE;
    // @formatter:on

    assertEquals(expected, actual);
  }
예제 #24
0
  @Test
  public void addFieldWithLongerLength() throws IOException {
    // @formatter:off
    String bibtexEntry =
        OS.NEWLINE
            + OS.NEWLINE
            + "@Article{test,"
            + OS.NEWLINE
            + "  author =  {BlaBla},"
            + OS.NEWLINE
            + "  journal = {International Journal of Something},"
            + OS.NEWLINE
            + "  number =  {1},"
            + OS.NEWLINE
            + "  note =    {some note},"
            + OS.NEWLINE
            + "}";
    // @formatter:on

    // read in bibtex string
    ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry));
    Collection<BibEntry> entries = result.getDatabase().getEntries();
    BibEntry entry = entries.iterator().next();

    // modify entry
    entry.setField("howpublished", "asdf");

    // write out bibtex string
    StringWriter stringWriter = new StringWriter();
    writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX);
    String actual = stringWriter.toString();

    // @formatter:off
    String expected =
        OS.NEWLINE
            + "@Article{test,"
            + OS.NEWLINE
            + "  author       = {BlaBla},"
            + OS.NEWLINE
            + "  journal      = {International Journal of Something},"
            + OS.NEWLINE
            + "  number       = {1},"
            + OS.NEWLINE
            + "  note         = {some note},"
            + OS.NEWLINE
            + "  howpublished = {asdf},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE;
    // @formatter:on
    assertEquals(expected, actual);
  }
예제 #25
0
  @Test
  public void writeEntriesInOriginalOrderWhenNoSaveOrderConfigIsSetInMetadata() throws Exception {
    BibEntry firstEntry = new BibEntry();
    firstEntry.setType(BibtexEntryTypes.ARTICLE);
    firstEntry.setField("author", "A");
    firstEntry.setField("year", "2010");

    BibEntry secondEntry = new BibEntry();
    secondEntry.setType(BibtexEntryTypes.ARTICLE);
    secondEntry.setField("author", "B");
    secondEntry.setField("year", "2000");

    BibEntry thirdEntry = new BibEntry();
    thirdEntry.setType(BibtexEntryTypes.ARTICLE);
    thirdEntry.setField("author", "A");
    thirdEntry.setField("year", "2000");

    database.insertEntry(firstEntry);
    database.insertEntry(secondEntry);
    database.insertEntry(thirdEntry);

    SavePreferences preferences = new SavePreferences().withSaveInOriginalOrder(false);
    StringSaveSession session =
        databaseWriter.savePartOfDatabase(bibtexContext, database.getEntries(), preferences);

    assertEquals(
        OS.NEWLINE
            + "@Article{,"
            + OS.NEWLINE
            + "  author = {A},"
            + OS.NEWLINE
            + "  year   = {2010},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE
            + OS.NEWLINE
            + "@Article{,"
            + OS.NEWLINE
            + "  author = {B},"
            + OS.NEWLINE
            + "  year   = {2000},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE
            + OS.NEWLINE
            + "@Article{,"
            + OS.NEWLINE
            + "  author = {A},"
            + OS.NEWLINE
            + "  year   = {2000},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE
            + OS.NEWLINE
            + "@Comment{jabref-meta: databaseType:bibtex;}"
            + OS.NEWLINE,
        session.getStringValue());
  }
예제 #26
0
  @Test
  public void doNotWriteEmptyFields() throws IOException {
    StringWriter stringWriter = new StringWriter();

    BibEntry entry = new BibEntry("1234", "article");
    entry.setField("author", "  ");
    entry.setField("note", "some note");

    writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX);

    String actual = stringWriter.toString();

    String expected =
        OS.NEWLINE
            + "@Article{,"
            + OS.NEWLINE
            + "  note   = {some note},"
            + OS.NEWLINE
            + "}"
            + OS.NEWLINE;

    assertEquals(expected, actual);
  }
예제 #27
0
 @Override
 public List<FieldChange> remove(List<BibEntry> entriesToRemove) {
   Objects.requireNonNull(entriesToRemove);
   List<FieldChange> changes = new ArrayList<>();
   for (BibEntry entry : entriesToRemove) {
     if (contains(entry)) {
       String oldContent = entry.getField(searchField).orElse("");
       KeywordList wordlist = KeywordList.parse(oldContent, keywordSeparator);
       wordlist.remove(searchExpression);
       String newContent = wordlist.getAsString(keywordSeparator);
       entry.setField(searchField, newContent).ifPresent(changes::add);
     }
   }
   return changes;
 }
예제 #28
0
  @Override
  public void redo() {
    super.redo();

    // Redo the change.
    try {
      if (newValue == null) {
        entry.clearField(field);
      } else {
        entry.setField(field, newValue);
      }

    } catch (IllegalArgumentException ex) {
      LOGGER.info("Cannot perform redo", ex);
    }
  }
예제 #29
0
  @Override
  public void undo() {
    super.undo();

    // Revert the change.
    try {
      if (oldValue == null) {
        entry.clearField(field);
      } else {
        entry.setField(field, oldValue);
      }

      // this is the only exception explicitly thrown here
    } catch (IllegalArgumentException ex) {
      LOGGER.info("Cannot perform undo", ex);
    }
  }
예제 #30
0
 @Override
 public List<FieldChange> cleanup(BibEntry entry) {
   ArrayList<FieldChange> changes = new ArrayList<>();
   final String[] fields = {"title", "author", "abstract"};
   for (String field : fields) {
     String oldValue = entry.getField(field);
     if (oldValue == null) {
       break;
     }
     final HTMLConverter htmlConverter = new HTMLConverter();
     String newValue = htmlConverter.formatUnicode(oldValue);
     if (!oldValue.equals(newValue)) {
       entry.setField(field, newValue);
       changes.add(new FieldChange(entry, field, oldValue, newValue));
     }
   }
   return changes;
 }