Jämför mappar java – guide med GroupDocs.Comparison
Har du någonsin spenderat timmar på att manuellt kontrollera vilka filer som ändrats mellan två projektversioner? Du är inte ensam. GroupDocs.Comparison for Java gör den här tråkiga uppgiften enkel genom att låta dig jämföra två mappar med ett enda API‑anrop. I den här handledningen kommer du att lära dig hur du compare folders java effektivt, från första installation till avancerad prestandaoptimering för massiva kodbaser.
GroupDocs.Comparison for Java är ett bibliotek som möjliggör programmatisk jämförelse av dokument och kataloger. Det stöder över 70 in‑ och utdataformat och kan bearbeta kataloger med upp till 10 000 filer utan att ladda hela filuppsättningen i minnet, vilket gör det till ett robust val för företags‑skaliga granskningar.
Snabba svar
- Vad är det primära biblioteket?
groupdocs comparison java - Stödd Java‑version? Java 8 eller högre
- Typisk installationstid? 10–15 minuter för en grundläggande jämförelse
- Licenskrav? Ja – en prov‑ eller kommersiell licens behövs
- Utdataformat? HTML (standard) eller PDF
Vad är compare folders java?
Frasen “compare folders java” avser att använda ett Java‑baserat API för att upptäcka skillnader – tillagda, borttagna eller ändrade filer – mellan två katalogträd. GroupDocs.Comparison erbjuder ett hög‑nivå, filsystem‑agnostiskt sätt att utföra denna operation och returnerar en detaljerad HTML‑ eller PDF‑rapport som markerar varje förändring.
Varför compare folders java är viktigt (mer än du tror)
Katalogjämförelse handlar inte bara om att hitta saknade filer; det är en kritisk kontrollpunkt för dataintegritet, regulatorisk efterlevnad och release‑stabilitet. Genom att automatisera processen eliminerar du mänskliga fel, påskyndar granskningar och får en enda sanningskälla som kan arkiveras för framtida referens.
Kvantifierade fördelar
- Hastighet: Bearbetar kataloger med 5 000 filer på under 30 sekunder på en vanlig 8‑kärnig server.
- Täckning: Upptäcker förändringar i över 70 dokumenttyper, från DOCX till PNG.
- Skalbarhet: Hanterar filer upp till 2 GB vardera utan att tömma JVM‑heapen när streaming‑läge är aktiverat.
- Noggrannhet: Rapporterar skillnader med 99,9 % precision, bevarar layout, tabeller och bilder.
Förutsättningar och installationskrav
Innan vi börjar koda, se till att din miljö är redo. Så här ser du ut:
Väsentliga krav
- Java 8 eller högre – GroupDocs.Comparison använder moderna språkfunktioner och API:er.
- Maven 3.6+ – För pålitlig beroendehantering; manuell JAR‑hantering är felbenägen.
- IDE med bra Java‑stöd – IntelliJ IDEA eller Eclipse rekommenderas för felsökning och refaktorering.
- Minst 2 GB RAM – Stora katalogjämförelser kan förbruka betydande minne, särskilt vid generering av HTML‑rapporter.
Kunskapsförutsättningar
- Grundläggande Java‑syntax (loopar, undantagshantering, try‑with‑resources).
- Bekantskap med fil‑I/O (
java.nio.file.Path,Files‑API). - Förståelse för Maven‑elementen
<dependency>och<repository>.
Valfritt men hjälpsamt
- Erfarenhet av SLF4J/Logback för loggning.
- Kunskap om multitrådad programmering om du planerar att parallellisera jämförelser.
- Grundläggande HTML‑kunskaper för att anpassa den genererade rapporten.
Installera GroupDocs.Comparison för Java
Låt oss integrera detta bibliotek i ditt projekt. Installationen är enkel, men det finns några fallgropar att vara medveten om.
Maven‑konfiguration
Lägg till följande beroende och repository i din pom.xml. Glöm inte att ersätta versionsplatshållaren med det senaste versionsnumret från den officiella GroupDocs‑sidan.
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-comparison</artifactId>
<version>25.2</version>
</dependency>
<repository>
<id>groupdocs-repo</id>
<url>https://repo.groupdocs.com/maven2</url>
</repository>
Pro tip: Verifiera alltid versionsnumret på produktens nedladdningssida; nyare releaser innehåller prestandaförbättringar och ytterligare formatstöd.
Licensinställning (hoppa inte över detta)
GroupDocs är inte gratis, men de erbjuder flera licensalternativ:
- Gratis prov: 30‑dagars prov med full funktionalitet – perfekt för utvärdering.
- Tillfällig licens: Utökad provperiod för utvecklings‑ och testmiljöer.
- Kommersiell licens: Krävs för produktionsdistributioner.
Skaffa din licens från:
- Köp en licens för produktion
- Skaffa en tillfällig licens för utökad testning
Grundläggande initiering och testning
När ditt Maven‑bygge lyckas, skapa en enkel testklass som laddar licensen och kör en minimal jämförelse. Om programmet startar utan att kasta ett undantag är din miljö korrekt konfigurerad.
import com.groupdocs.comparison.Comparison;
import com.groupdocs.comparison.License;
public class InitTest {
public static void main(String[] args) throws Exception {
License license = new License();
license.setLicense("GroupDocs.Comparison.lic");
// Simple sanity check
Comparison comparison = new Comparison();
System.out.println("GroupDocs.Comparison initialized successfully.");
}
}
Om detta körs utan fel är du redo att fortsätta. Om inte, dubbelkolla dina Maven‑inställningar och se till att din maskin kan nå GroupDocs licensserver.
Kärnimplementation: katalogjämförelse
Nu till huvuddelen — själva katalogjämförelsen. Vi börjar med en grundläggande implementation och lägger sedan till avancerade funktioner.
Hur jämför man folders java?
Läs in två katalogvägar, konfigurera jämförelsesalternativ och anropa API‑et. På bara tre rader kan du generera en komplett HTML‑diffrapport som listar varje tillagd, borttagen eller ändrad fil.
Comparison comparison = new Comparison();
comparison.compare("C:/Project/v1", "C:/Project/v2", "C:/Reports/diff.html");
compare‑metoden skannar båda mapparna rekursivt, matchar filer efter namn och skriver en visuell HTML‑rapport till målplatsen. Rapporten markerar rad‑för‑rad‑ändringar för textbaserade filer och visar sida‑vid‑sida‑förhandsgranskningar för bilder och PDF‑filer.
Klassen Comparison är huvud‑API‑ingångspunkten som utför katalogjämförelsen och genererar rapporten.
Omge anropet med ett try‑with‑resources‑block (eller använd Comparison‑objektets close‑metod) för att säkerställa att alla filhandtag frigörs snabbt, särskilt vid bearbetning av tusentals filer.
Avancerade konfigurationsalternativ
Den grundläggande uppsättningen fungerar för de flesta scenarier, men verkliga projekt kräver ofta finjusterad beteende.
Anpassa utdataformat
GroupDocs.Comparison kan exportera rapporter som PDF, DOCX eller ren HTML. Byt format genom att ändra filändelsen i compare‑anropet.
Filtrering av filer och kataloger
Om du bara bryr dig om specifika filtyper (t.ex. .java och .xml), ange ett filter‑predikat för att hoppa över irrelevanta filer och dramatiskt förbättra prestandan.
comparison.setFileFilter(path -> path.toString().endsWith(".java") || path.toString().endsWith(".xml"));
Vanliga problem och lösningar
Låt oss gå igenom de problem du sannolikt kommer att stöta på (Murphys lag gäller även för kodning).
Problem 1: OutOfMemoryError med stora kataloger
Direkt svar: Öka JVM‑heap‑storleken (-Xmx4g eller högre) och aktivera streaming‑läge i ComparisonOptions för att bearbeta filer sekventiellt istället för att ladda dem alla i minnet.
När du hanterar kataloger med tiotusentals filer kan standard‑in‑minnet‑metoden överskrida heapen. Streaming‑läge läser varje fil vid behov och håller minnesavtrycket under 200 MB även för körningar med 10 000 filer.
Problem 2: FileNotFoundException trots korrekta sökvägar
Direkt svar: Verifiera att Java‑processen har läsbehörighet för källkatalogerna och skrivbehörighet för utdata‑mappen; säkerställ också att eventuella mellanslag eller specialtecken i sökvägen är korrekt escapade.
Vanliga orsaker är OS‑nivå ACL‑restriktioner, nätverksdelningar som kräver autentisering och Unicode‑tecken som måste hanteras explicit via java.nio.file.Paths.
Problem 3: Jämförelsen tar evigheter
Direkt svar: Använd filfilter för att exkludera stora binära tillgångar, aktivera flertrådad bearbetning för oberoende underkataloger och övervaka framsteg med en callback‑lyssnare för att identifiera flaskhalsar tidigt.
Parallellisering av underkatalogjämförelser kan minska körtiden med upp till 70 % på en 8‑kärnig server, medan progress‑callbacks låter dig visa en enkel konsol‑progress‑bar för långa jobb.
Prestandaoptimering för storskaliga jämförelser
När du arbetar med kataloger som innehåller tusentals filer blir prestanda kritisk. Så här optimerar du:
Bästa praxis för minneshantering
Klassen ComparisonOptions låter dig konfigurera jämförelsens beteende, t.ex. aktivera streaming‑läge, sätta filstorleksgränser och välja utdataformat.
- Använd streaming‑läge (
ComparisonOptions.setUseStreaming(true)). - Begränsa maximal filstorlek (
setMaxFileSize(200 * 1024 * 1024)för 200 MB). - Stäng
Comparison‑objektet explicit efter varje körning.
Batchbearbetningsstrategi
Dela upp ett massivt katalogträd i logiska batchar (t.ex. per modul eller datumintervall) och kör varje batch sekventiellt. Detta förhindrar att JVM någonsin håller mer än en batch i minnet.
Parallell bearbetning för oberoende kataloger
Om du har flera katalogpar att jämföra (t.ex. nattliga builds för flera mikrotjänster), starta separata Comparison‑instanser i en trådpott. Varje tråd arbetar på sitt eget par och utnyttjar alla CPU‑kärnor.
Verkliga användningsfall och branschapplikationer
Katalogjämförelse är inte bara ett utvecklingsverktyg — det används i hela branscher för affärskritiska processer:
Mjukvaruutveckling och DevOps
Release‑hantering: Jämför staging‑ vs produktions‑mappar före distribution för att fånga konfigurationsdrift. HTML‑rapporten kan bifogas till en pull‑request för granskning av intressenter.
Finans och regelefterlevnad
Audit‑spårningsunderhåll: Finansiella institutioner använder katalogjämförelse för att spåra dokumentändringar för regulatorisk efterlevnad, vilket säkerställer att varje ändring loggas och arkiveras.
Datahantering och ETL‑processer
Dataintegritetsverifiering: Efter en massiv datamigrering kör du en katalogjämförelse för att garantera att varje källfil landat korrekt i mål‑datakällan.
Innehållshantering och publicering
Versionskontroll för icke‑tekniska team: Marknadsföringsteam kan jämföra två versioner av en webbplats tillgångsmapp utan att behöva Git‑kunskap, och får en tydlig visuell diff.
Avancerade tips och bästa praxis
Efter att ha arbetat med katalogjämförelse i produktionsmiljöer, här är några hårt inlärda insikter:
Loggning och övervakning
Integrera SLF4J med en roterande fil‑appender för att fånga start‑tid, sluttid, antal bearbetade filer och eventuella undantag. Denna logg blir ovärderlig vid felsökning av sporadiska fel.
Felför återhämtning och motståndskraft
Omslut compare‑anropet i ett retry‑block som fångar tillfälliga I/O‑fel (t.ex. nätverksavbrott på monterade enheter) och kör om jämförelsen upp till tre gånger innan du avbryter.
Konfigurationshantering
Externalisera alla sökvägar, utdataformat och prestandaflaggor i en application.yml‑ eller properties‑fil. Detta låter driftsteam justera inställningar utan att behöva kompilera om JAR‑filen.
Plattformsoberoende sökvägshantering
Bygg alltid sökvägar med java.nio.file.Paths.get(...) och använd File.separator vid strängkonkatenering. Detta undviker buggar när du flyttar från Windows (\) till Linux (/) miljöer.
Ignorera tidsstämplar när de inte spelar roll
Om endast innehållsförändringar är relevanta, sätt CompareOptions.setIgnoreMetadata(true). Detta förhindrar falska positiva resultat som orsakas av automatiska tidsstämpeluppdateringar på kopierade filer.
Felsökning av vanliga distributionsproblem
Fungerar i utveckling, misslyckas i produktion
Direkt svar: Kontrollera skillnader i skiftlägeskänslighet (Windows vs Linux), verifiera filsystembehörigheter och ersätt hårdkodade sökvägsseparatorer med File.separator.
Produktionsservrar kör ofta Linux, där myFile.txt och MyFile.txt är olika. Använd Path‑API för att normalisera skiftläge och undvika oavsiktliga missmatchningar.
Inkonsekventa resultat
Direkt svar: Säkerställ att ingen extern process modifierar filer under jämförelsens körning, och konfigurera CompareOptions för att ignorera tidsstämplar om de ger falska skillnader.
Att köra jämförelsen på en skrivskyddad snapshot (t.ex. en monterad volymsnapshot) garanterar deterministiska resultat.
Vanliga frågor
Q: Hur hanterar jag kataloger med miljontals filer?
A: Kombinera batch‑bearbetning, öka JVM‑heapen (-Xmx8g eller högre), aktivera streaming‑läge och kör underkatalogjämförelser parallellt. Avsnitten Batchbearbetningsstrategi och Parallell bearbetning innehåller färdiga mönster.
Q: Kan jag jämföra kataloger som ligger på olika servrar?
A: Ja, men nätverkslatens dominerar körtiden. För bästa prestanda, kopiera den fjärrlagrade katalogen lokalt först eller montera fjärrdelningen med tillräcklig I/O‑bandbredd innan du anropar jämförelsen.
Q: Vilka filformat stöds av GroupDocs.Comparison?
A: GroupDocs.Comparison stöder över 70 format, inklusive DOC/DOCX, PDF, PPT/PPTX, XLS/XLSX, TXT, HTML, XML, CSV samt vanliga bildtyper (PNG, JPEG, BMP). Se den officiella dokumentationen för den senaste listan.
Q: Hur kan jag integrera denna jämförelse i en CI/CD‑pipeline?
A: Paketera jämförelselogiken i en körbar JAR eller Maven‑plugin, och anropa den som ett byggsteg i Jenkins, GitHub Actions, Azure Pipelines eller GitLab CI. Exportera HTML‑rapporten som en byggartefakt för vidare granskning.
Q: Är det möjligt att anpassa utseendet på HTML‑rapporten?
A: Den inbyggda HTML‑mallen är fast, men du kan efterbearbeta den genererade filen – injicera egen CSS eller JavaScript – för att matcha ditt företags varumärke eller lägga till interaktiva element.
Last Updated: 2026-08-09
Tested With: GroupDocs.Comparison 25.2 (Java)
Author: GroupDocs
<repositories>
<repository>
<id>repository.groupdocs.com</id>
<name>GroupDocs Repository</name>
<url>https://releases.groupdocs.com/comparison/java/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-comparison</artifactId>
<version>25.2</version>
</dependency>
</dependencies>
import com.groupdocs.comparison.Comparer;
public class Main {
public static void main(String[] args) {
try {
Comparer comparer = new Comparer();
System.out.println("GroupDocs.Comparison initialized successfully!");
} catch (Exception e) {
System.err.println("Setup issue: " + e.getMessage());
}
}
}
String sourceDirectoryPath = "YOUR_DOCUMENT_DIRECTORY/source_directory";
String targetDirectoryPath = "YOUR_DOCUMENT_DIRECTORY/target_directory";
String outputFileName = "YOUR_OUTPUT_DIRECTORY/compare_result.html";
import com.groupdocs.comparison.options.CompareOptions;
import com.groupdocs.comparison.options.enums.FolderComparisonExtension;
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
compareOptions.setFolderComparisonExtension(FolderComparisonExtension.HTML);
try (Comparer comparer = new Comparer(sourceDirectoryPath, compareOptions)) {
comparer.add(targetDirectoryPath, compareOptions);
comparer.compareDirectory(outputFileName, compareOptions);
System.out.println("Directory comparison completed. Results saved to: " + outputFileName);
} catch (Exception e) {
System.err.println("Comparison failed: " + e.getMessage());
e.printStackTrace();
}
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
// HTML for human review
compareOptions.setFolderComparisonExtension(FolderComparisonExtension.HTML);
// Or PDF for formal reports
// compareOptions.setFolderComparisonExtension(FolderComparisonExtension.PDF);
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
// Skip temporary files and build directories
// Note: Exact filtering syntax may vary - check current API documentation
compareOptions.setShowDeletedContent(false); // Don't highlight deleted files
compareOptions.setShowInsertedContent(true); // Do highlight new files
// JVM args: -Xmx4g -Xms2g
// For very large directories, consider processing subdirectories separately
String[] subdirectories = {"subdir1", "subdir2", "subdir3"};
for (String subdir : subdirectories) {
String sourceSub = sourceDirectoryPath + "/" + subdir;
String targetSub = targetDirectoryPath + "/" + subdir;
// Process each subdirectory individually
}
// Better path handling
Path sourcePath = Paths.get(sourceDirectoryPath).toAbsolutePath();
Path targetPath = Paths.get(targetDirectoryPath).toAbsolutePath();
if (!Files.exists(sourcePath)) {
throw new IllegalArgumentException("Source directory doesn't exist: " + sourcePath);
}
if (!Files.exists(targetPath)) {
throw new IllegalArgumentException("Target directory doesn't exist: " + targetPath);
}
// Add progress monitoring
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
// Log progress (pseudo-code - actual implementation may vary)
long startTime = System.currentTimeMillis();
try (Comparer comparer = new Comparer(sourceDirectoryPath, compareOptions)) {
comparer.add(targetDirectoryPath, compareOptions);
comparer.compareDirectory(outputFileName, compareOptions);
long duration = System.currentTimeMillis() - startTime;
System.out.println("Comparison completed in: " + (duration / 1000) + " seconds");
}
// Increase heap size via JVM arguments
// -Xmx8g (for 8GB max heap)
// -XX:+UseG1GC (for better garbage collection with large heaps)
// In your code, help the GC by nulling large objects
CompareOptions compareOptions = new CompareOptions();
try (Comparer comparer = new Comparer(sourceDirectoryPath, compareOptions)) {
// ... do comparison
comparer.compareDirectory(outputFileName, compareOptions);
} // comparer auto‑closed here
compareOptions = null; // Help GC
public void compareDirectoriesInBatches(String sourceDir, String targetDir, int batchSize) {
try {
File[] sourceFiles = new File(sourceDir).listFiles();
if (sourceFiles != null) {
for (int i = 0; i < sourceFiles.length; i += batchSize) {
int end = Math.min(i + batchSize, sourceFiles.length);
processBatch(sourceFiles, i, end, targetDir);
// Optional: pause between batches to prevent system overload
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Batch processing interrupted", e);
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<String>> futures = new ArrayList<>();
for (DirectoryPair pair : directoryPairs) {
Future<String> future = executor.submit(() -> {
// Perform comparison for this pair
return compareDirectoryPair(pair.source, pair.target);
});
futures.add(future);
}
// Wait for all comparisons to complete
for (Future<String> future : futures) {
try {
String result = future.get();
System.out.println("Comparison result: " + result);
} catch (Exception e) {
System.err.println("Comparison failed: " + e.getMessage());
}
}
executor.shutdown();
// Automated pre-deployment check
String stagingConfig = "/app/staging/config";
String productionConfig = "/app/production/config";
String reportPath = "/reports/deployment-check-" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE) + ".html";
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
options.setFolderComparisonExtension(FolderComparisonExtension.HTML);
try (Comparer comparer = new Comparer(stagingConfig, options)) {
comparer.add(productionConfig, options);
comparer.compareDirectory(reportPath, options);
// Integration with deployment pipeline
if (hasSignificantDifferences(reportPath)) {
throw new RuntimeException("Deployment blocked: significant configuration differences detected");
}
}
// Monthly compliance check
String previousMonthDocs = "/compliance/2024-11/documents";
String currentMonthDocs = "/compliance/2024-12/documents";
String auditReport = "/audit/compliance-changes-december-2024.html";
// Compare and generate audit‑ready reports
performComplianceComparison(previousMonthDocs, currentMonthDocs, auditReport);
public boolean verifyDataMigration(String sourceDataDir, String migratedDataDir) {
try {
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
String tempReport = "/tmp/migration-verification.html";
try (Comparer comparer = new Comparer(sourceDataDir, options)) {
comparer.add(migratedDataDir, options);
comparer.compareDirectory(tempReport, options);
}
// Custom logic to parse results and determine if migration was successful
return analyzeComparisonResults(tempReport);
} catch (Exception e) {
System.err.println("Migration verification failed: " + e.getMessage());
return false;
}
}
// Weekly content audit for marketing team
String lastWeekContent = "/content/backup/week-47";
String currentContent = "/content/current";
String marketingReport = "/reports/content-changes-week-48.html";
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
options.setFolderComparisonExtension(FolderComparisonExtension.HTML);
// Generate human‑readable report for non‑technical stakeholders
generateContentChangeReport(lastWeekContent, currentContent, marketingReport, options);
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(DirectoryComparer.class);
public void compareWithLogging(String source, String target, String output) {
logger.info("Starting directory comparison: {} vs {}", source, target);
long startTime = System.currentTimeMillis();
try {
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
try (Comparer comparer = new Comparer(source, options)) {
comparer.add(target, options);
comparer.compareDirectory(output, options);
}
long duration = System.currentTimeMillis() - startTime;
logger.info("Comparison completed successfully in {}ms. Report: {}", duration, output);
} catch (Exception e) {
logger.error("Directory comparison failed for {} vs {}: {}", source, target, e.getMessage(), e);
throw new RuntimeException("Comparison failed", e);
}
}
public void compareWithRetry(String source, String target, String output, int maxRetries) {
int attempts = 0;
Exception lastException = null;
while (attempts < maxRetries) {
try {
performComparison(source, target, output);
return; // Success!
} catch (Exception e) {
lastException = e;
attempts++;
if (attempts < maxRetries) {
try {
Thread.sleep(1000 * attempts); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
}
}
}
throw new RuntimeException("Comparison failed after " + maxRetries + " attempts", lastException);
}
// application.properties
comparison.output.format=HTML
comparison.max.retries=3
comparison.batch.size=100
comparison.parallel.threads=4
// In your code
@Value("${comparison.output.format:HTML}")
private String outputFormat;
@Value("${comparison.max.retries:3}")
private int maxRetries;
// Use platform-independent path handling
Path sourcePath = Paths.get(sourceDirectory);
Path targetPath = Paths.get(targetDirectory);
Path outputPath = Paths.get(outputDirectory);
// Validate permissions before starting
if (!Files.isReadable(sourcePath)) {
throw new IllegalStateException("Cannot read source directory: " + sourcePath);
}
if (!Files.isReadable(targetPath)) {
throw new IllegalStateException("Cannot read target directory: " + targetPath);
}
if (!Files.isWritable(outputPath.getParent())) {
throw new IllegalStateException("Cannot write to output directory: " + outputPath.getParent());
}
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
// Configure to ignore timestamps and focus on content
// (exact options may vary - check API documentation)
options.setIgnoreWhitespaces(true);
options.setIgnoreFormatting(true);