From 12c21826a6ec0de7b13c0f44f185e8608f16862a Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Mon, 27 Jul 2026 15:59:40 -0700 Subject: [PATCH 1/2] Fix Zim header regex on Android --- .../markor/format/wikitext/WikitextSyntaxHighlighter.java | 2 +- .../markor/format/wikitext/WikitextFileTests.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java b/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java index 65a396e91..c9d441815 100644 --- a/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java +++ b/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java @@ -45,7 +45,7 @@ public class WikitextSyntaxHighlighter extends SyntaxHighlighterBase { public final static Pattern ZIMHEADER = Pattern.compile( "^Content-Type: text/x-zim-wiki(\r\n|\r|\n)" + "Wiki-Format: zim \\d+\\.\\d+(\r\n|\r|\n)" + - "Creation-Date: \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[.+:-\\d]+"); + "Creation-Date: \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[-.+:\\d]+"); // groups for matching individual parts of the checklist regex public static final int CHECKBOX_LEFT_BRACKET_GROUP = 2; diff --git a/app/src/test/java/net/gsantner/markor/format/wikitext/WikitextFileTests.java b/app/src/test/java/net/gsantner/markor/format/wikitext/WikitextFileTests.java index 2f443f718..5f7164828 100644 --- a/app/src/test/java/net/gsantner/markor/format/wikitext/WikitextFileTests.java +++ b/app/src/test/java/net/gsantner/markor/format/wikitext/WikitextFileTests.java @@ -57,6 +57,14 @@ public void zimHeaderAtBeginningOfTheFileShouldMatch() { assertThat(matcher.group()).isEqualTo("Content-Type: text/x-zim-wiki\nWiki-Format: zim 0.4\nCreation-Date: 2019-03-31T14:48:06+02:00"); } + @Test + public void zimHeaderWithNegativeTimezoneOffsetShouldMatch() { + pattern = WikitextSyntaxHighlighter.ZIMHEADER; + Matcher matcher = pattern.matcher("Content-Type: text/x-zim-wiki\nWiki-Format: zim 0.4\nCreation-Date: 2019-03-31T14:48:06-07:00\nOther content..."); + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group()).isEqualTo("Content-Type: text/x-zim-wiki\nWiki-Format: zim 0.4\nCreation-Date: 2019-03-31T14:48:06-07:00"); + } + @Test public void zimHeaderNotAtBeginningOfTheFileShouldNotMatch() { pattern = WikitextSyntaxHighlighter.ZIMHEADER; From 4f63ffd11db3f99bc1bdbd638e462b6fc77b6ce9 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Mon, 27 Jul 2026 16:10:53 -0700 Subject: [PATCH 2/2] Skip format detection when format is known --- .../format/todotxt/TodoTxtActionButtons.java | 5 ++-- .../wikitext/WikitextSyntaxHighlighter.java | 1 - .../wikitext/WikitextTextConverter.java | 4 ++- .../markor/frontend/NewFileDialog.java | 2 +- .../net/gsantner/markor/model/Document.java | 16 +++++++++--- .../model/DocumentConstructionTests.java | 25 +++++++++++++++++++ 6 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 app/src/test/java/net/gsantner/markor/model/DocumentConstructionTests.java diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java index 4c5199203..b6651d8cf 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java @@ -21,6 +21,7 @@ import net.gsantner.markor.R; import net.gsantner.markor.format.ActionButtonBase; +import net.gsantner.markor.format.FormatRegistry; import net.gsantner.markor.frontend.MarkorDialogFactory; import net.gsantner.markor.frontend.textview.TextViewUtils; import net.gsantner.markor.model.Document; @@ -242,7 +243,7 @@ public void archiveDoneTasks() { doneContents.append(TodoTxtTask.tasksToString(move)).append("\n"); // Write to done file - if (new Document(doneFile).saveContent(getActivity(), doneContents.toString())) { + if (new Document(doneFile, FormatRegistry.FORMAT_TODOTXT).saveContent(getActivity(), doneContents.toString())) { final String tasksString = TodoTxtTask.tasksToString(keep); _hlEditor.setText(tasksString); TextViewUtils.setSelectionFromOffsets(_hlEditor, offsets); @@ -514,4 +515,4 @@ public void onCreate(final Bundle savedInstanceState) { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java b/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java index c9d441815..f0893d3d2 100644 --- a/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java +++ b/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextSyntaxHighlighter.java @@ -41,7 +41,6 @@ public class WikitextSyntaxHighlighter extends SyntaxHighlighterBase { public final static Pattern CHECKLIST_LEFT_ARROW = Pattern.compile("(?<=(\\n|^))\t*(\\[)(<)(])(?= )"); public final static Pattern SUBSCRIPT = Pattern.compile("(_\\{(?!~)(.+?)\\})"); public final static Pattern SUPERSCRIPT = Pattern.compile("(\\^\\{(?!~)(.+?)\\})"); - public final static Pattern ZIMHEADER_CONTENT_TYPE_ONLY = Pattern.compile("^\\s*Content-Type:\\s*text/x-zim-wiki"); public final static Pattern ZIMHEADER = Pattern.compile( "^Content-Type: text/x-zim-wiki(\r\n|\r|\n)" + "Wiki-Format: zim \\d+\\.\\d+(\r\n|\r|\n)" + diff --git a/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextTextConverter.java b/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextTextConverter.java index 11ccb3a3e..597f4cac4 100644 --- a/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextTextConverter.java +++ b/app/src/main/java/net/gsantner/markor/format/wikitext/WikitextTextConverter.java @@ -36,6 +36,8 @@ */ @SuppressWarnings("WeakerAccess") public class WikitextTextConverter extends TextConverterBase { + private static final Pattern ZIMHEADER_CONTENT_TYPE_ONLY = Pattern.compile("^\\s*Content-Type:\\s*text/x-zim-wiki"); + // Zim Source View plugin: {{{code: lang="..." ...\n...\n}}} private static final Pattern CODE_BLOCK_SOURCE_VIEW = Pattern.compile( "(?ms)^\\{\\{\\{code:([^\\r\\n]*)(?:\\r\\n?|\\n)(.*?)^\\}\\}\\}[ \\t]*$"); @@ -270,7 +272,7 @@ private String convertImage(final File file, final Context context, final String protected boolean isFileOutOfThisFormat(final File file, final String name, final String ext) { if (ext.equals(".txt")) { try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - return WikitextSyntaxHighlighter.ZIMHEADER_CONTENT_TYPE_ONLY.matcher(reader.readLine()).find(); + return ZIMHEADER_CONTENT_TYPE_ONLY.matcher(reader.readLine()).find(); } catch (Exception ignored) { } } diff --git a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java index f27cc7f91..431636aa6 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java +++ b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java @@ -306,7 +306,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { final File file = new File(basedir, fileName); // Most of the logic we want is in the document class so we just reuse it - final Document document = new Document(file); + final Document document = new Document(file, fmt.format); // These are done even if the file isn't created final String titleFormat = formatEdit.getText().toString().trim(); diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index 6482b7210..2fac01145 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -71,10 +71,7 @@ public class Document implements Serializable { private int _lastLength = -1; public Document(@NonNull final File f) { - path = GsFileUtils.getPath(f); - file = new File(path); - title = GsFileUtils.getFilenameWithoutExtension(file); - extension = GsFileUtils.getFilenameExtension(file); + this(f, FormatRegistry.FORMAT_UNKNOWN); // Set initial format for (final FormatRegistry.Format format : FormatRegistry.FORMATS) { @@ -85,6 +82,17 @@ public Document(@NonNull final File f) { } } + /** + * Create a document with a known format, skipping automatic format detection. + */ + public Document(@NonNull final File f, @StringRes final int format) { + path = GsFileUtils.getPath(f); + file = new File(path); + title = GsFileUtils.getFilenameWithoutExtension(file); + extension = GsFileUtils.getFilenameExtension(file); + setFormat(format); + } + private void initModTimePref() { // We do not do this in constructor as we want to init after deserialization too if (_modTimePref == null) { diff --git a/app/src/test/java/net/gsantner/markor/model/DocumentConstructionTests.java b/app/src/test/java/net/gsantner/markor/model/DocumentConstructionTests.java new file mode 100644 index 000000000..1758b4249 --- /dev/null +++ b/app/src/test/java/net/gsantner/markor/model/DocumentConstructionTests.java @@ -0,0 +1,25 @@ +/*####################################################### + * + * SPDX-FileCopyrightText: 2026 Harshad Vedartham + * SPDX-License-Identifier: Apache-2.0 + * +#########################################################*/ +package net.gsantner.markor.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import net.gsantner.markor.format.FormatRegistry; + +import org.junit.Test; + +import java.io.File; + +public class DocumentConstructionTests { + + @Test + public void knownFormatConstructorSetsFormatForAmbiguousFilename() { + final Document document = new Document(new File("archive.txt"), FormatRegistry.FORMAT_TODOTXT); + + assertThat(document.getFormat()).isEqualTo(FormatRegistry.FORMAT_TODOTXT); + } +}