Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix conformance to Zim (and enable Insert Audio button): wrong attachment directories, linked file not found, missing image link's tags, minsdk 16-18 #2147

Merged
merged 1 commit into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@ Where:
* **[Li Guanglin](https://github.com/guanglinn)**<br/>~° Added line numbers support
* **[bigger124](https://github.com/bigger124)**<br>~° Added OrgMode-Support
* **[Ayowel](https://github.com/ayowel)**<br>~° Mermaid update
* **[Matthew White](https://github.com/mehw)**<br>~° Zim-Wiki link/attachment conformance.
* **[Markus Paintner](https://github.com/goli4thus)**<br/>~° Added duplicate lines action
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public List<ActionItem> getFormatActionList() {
new ActionItem(R.string.abid_common_deindent, R.drawable.ic_format_indent_decrease_black_24dp, R.string.deindent),
new ActionItem(R.string.abid_wikitext_h4, R.drawable.format_header_4, R.string.heading_4),
new ActionItem(R.string.abid_wikitext_h5, R.drawable.format_header_5, R.string.heading_5),
new ActionItem(R.string.abid_common_insert_audio, R.drawable.ic_keyboard_voice_black_24dp, R.string.audio),
new ActionItem(R.string.abid_common_insert_image, R.drawable.ic_image_black_24dp, R.string.insert_image),
new ActionItem(R.string.abid_common_insert_link, R.drawable.ic_link_black_24dp, R.string.insert_link)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.apache.commons.io.FilenameUtils;

import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -90,7 +91,8 @@ private String resolveWikitextPath(String wikitextPath) {
if (_shouldDynamicallyDetermineRoot) {
_notebookRootDir = findNotebookRootDir(_currentPage);
if (_notebookRootDir == null) {
return null;
// try the current directory as a possible notebook root dir
_notebookRootDir = _currentPage.getParentFile();
}
}

Expand All @@ -107,7 +109,10 @@ private String resolveWikitextPath(String wikitextPath) {
return findFirstPageTraversingUpToRoot(_currentPage, relativeLinkToCheck);
}

return wikitextPath; // just return the original path in case the link cannot be resolved (might be a URL)
// Try to resolve the path as relative to the wiki page's attachment directory.
// Return an absolute path, or the original path in case it cannot be resolved,
// it might be a URL.
return resolveAttachmentPath(wikitextPath, _notebookRootDir, _currentPage, _shouldDynamicallyDetermineRoot);
}

private String stripInnerPageReference(String wikitextPath) {
Expand All @@ -119,7 +124,7 @@ private String stripInnerPageReference(String wikitextPath) {
return wikitextPath;
}

private File findNotebookRootDir(File currentPage) {
private static File findNotebookRootDir(File currentPage) {
if (currentPage != null && currentPage.exists()) {
if (GsFileUtils.join(currentPage, "notebook.zim").exists()) {
return currentPage;
Expand Down Expand Up @@ -176,4 +181,148 @@ public boolean isWebLink() {
public File getNotebookRootDir() {
return _notebookRootDir;
}

/**
* Return a wiki file's Attachment Directory.<p></p>
*
* <p> By design, a Zim wiki file's Attachment Directory should
* have the same path of the wiki file itself, without the .txt
* suffix.</p><p></p>
*
* <p>Here, a difference from Zim is that any file can derive a
* properly named Attachment Directory, as long as that file is
* with a suffix.</p>
*
* @param currentPage Current wiki file's path.
* @return {@code currentPage}'s path without suffix.
*
* @see GsFileUtils#getFilenameWithoutExtension(File)
*/
public static File findAttachmentDir(File currentPage) {
return GsFileUtils.join(currentPage.getParentFile(), GsFileUtils.getFilenameWithoutExtension(currentPage));
}

/**
* Return a Notebook's Root Directory.<p></p>
*
* <p> By design ({@code shouldDynamicallyDetermineRoot = true}),
* a Zim Notebook's Root Directory is the closest ancestor of the
* {@code currentPage} with a notebook.zim file in it.</p><p></p>
*
* <p> Here, a difference from Zim is that if a notebook.zim file
* can't be located, a {@code currentPage}'s current directory is
* taken as Root Directory.</p><p></p>
*
* <p> WARNING: Removing a notebook.zim file from the Notebook or
* changing the value of {@code notebookRootDir}, may switch to a
* Root Directory different than where the Notebook was organized
* originally.</p>
*
* @param notebookRootDir Root Directory when {@code shouldDynamicallyDetermineRoot == false}.
* @param currentPage Current wiki file's path used to determine the current directory.
* @param shouldDynamicallyDetermineRoot If {@code true}, return the closest ancestor of the
* {@code currentPage} with a notebook.zim file in it, or fall back to the current directory
* on failure. If {@code false}, return {@code notebookRootDir}.
* @return Identified Notebook's Root Directory.
*
* @see WikitextLinkResolver#findNotebookRootDir(File)
*/
public static File findNotebookRootDir(File notebookRootDir, File currentPage, boolean shouldDynamicallyDetermineRoot) {
if (shouldDynamicallyDetermineRoot) {
notebookRootDir = findNotebookRootDir(currentPage);
if (notebookRootDir == null) {
notebookRootDir = currentPage.getParentFile();
}
}
return notebookRootDir;
}

/**
* Return a system file's path as wiki attachment's path.<p></p>
*
* <p> If both {@code file} and {@code currentPage} are children
* of the same Notebook's Root Directory, return a path relative
* to the {@code currentPage}'s Attachment Directory, otherwise,
* return the original {@code file}'s path.</p><p></p>
*
* <p> Here, a difference from Zim is to always consider as Root
* Directory the {@code currentPage}'s directory before anything
* else.</p>
*
* @param file System file's path to resolve as wiki attachment's path.
* @param notebookRootDir Root Directory when {@code shouldDynamicallyDetermineRoot == false}.
* @param currentPage Current wiki file's path used to determine the current Attachment Directory.
* @param shouldDynamicallyDetermineRoot If {@code true}, the Root Directory is the closest ancestor
* of the {@code currentPage} with a notebook.zim file in it, or the {@code currentPage}'s directory
* on failure. If {@code false}, the Root Directory is {@code notebookRootDir}. In either cases, a
* {@code currentPage}'s directory is always considered as Root Directory before anything else.
* @return {@code file}'s path relative to the {@code currentPage}'s Attachment Directory, when both
* {@code file} and {@code currentPage} are children of the identified Notebook's Root Directory, or
* the original {@code file}'s path otherwise.
*
* @see WikitextLinkResolver#findAttachmentDir(File)
* @see WikitextLinkResolver#findNotebookRootDir(File, File, boolean)
*/
public static String resolveSystemFilePath(File file, File notebookRootDir, File currentPage, boolean shouldDynamicallyDetermineRoot) {
final File currentDir = currentPage.getParentFile();
notebookRootDir = findNotebookRootDir(notebookRootDir, currentPage, shouldDynamicallyDetermineRoot);

if (GsFileUtils.isChild(currentDir, file) ||
(GsFileUtils.isChild(notebookRootDir, file) && GsFileUtils.isChild(notebookRootDir, currentPage))) {
final File attachmentDir = findAttachmentDir(currentPage);
String path = GsFileUtils.relativePath(attachmentDir, file);

// Zim prefixes also children of the Attachment Directory.
if (file.toString().endsWith("/" + path)) {
path = "./" + path;
}

return path;
}

return file.toString();
}

/**
* Return a wiki attachment's path as system absolute path.<p></p>
*
* <p> If {@code path} can be resolved as a relative path to the
* {@code currentPage}'s Attachment Directory, return the result
* of {@code path} as a system absolute path. Otherwise, return
* the original {@code path}.</p><p></p>
*
* <p> Here, a difference from Zim is to always consider as Root
* Directory the {@code currentPage}'s directory before anything
* else.</p>
*
* @param path Path that might be relative to the {@code currentPage}'s Attachment Directory.
* @param notebookRootDir Root Directory when {@code shouldDynamicallyDetermineRoot == false}.
* @param currentPage Current wiki file's path used to determine the current Attachment Directory.
* @param shouldDynamicallyDetermineRoot If {@code true}, the Root Directory is the closest ancestor
* of the {@code currentPage} with a notebook.zim file in it, or the {@code currentPage}'s directory
* on failure. If {@code false}, the Root Directory is {@code notebookRootDir}. In either cases, a
* {@code currentPage}'s directory is always considered as Root Directory before anything else.
* @return {@code path} as a system absolute path, if it can be resolved as a relative path to the
* {@code currentPage}'s Attachment Directory, or the original {@code path} otherwise.
*
* @see WikitextLinkResolver#findAttachmentDir(File)
* @see WikitextLinkResolver#resolveSystemFilePath(File, File, File, boolean)
*/
public static String resolveAttachmentPath(String path, File notebookRootDir, File currentPage, boolean shouldDynamicallyDetermineRoot) {
if (path.startsWith("./") || path.startsWith("../")) {
final File attachmentDir = findAttachmentDir(currentPage);
final File file = new File(attachmentDir, path).getAbsoluteFile();
final String resolved = resolveSystemFilePath(file, notebookRootDir, currentPage, shouldDynamicallyDetermineRoot);

if (path.equals(resolved)) {
try {
return file.getCanonicalPath();
} catch (IOException e) {
return file.toString();
}
}
}

return path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -155,7 +158,49 @@ private String convertImage(File file, String fullMatch) {
String currentPageFileName = file.getName();
String currentPageFolderName = currentPageFileName.replaceFirst(".txt$", "");
String markdownPathToImage = FilenameUtils.concat(currentPageFolderName, imagePathFromPageFolder);
return "![" + file.getName() + "](" + markdownPathToImage + ")";

// Zim may insert in the image link, after a '?' character, the 'id', 'width',
// 'height', 'type', and 'href' tags, separating them with a '&' character, so
// you may not want to use '?' and '&' as directory or file name:
// https://github.com/zim-desktop-wiki/zim-desktop-wiki/blob/c88cf3cb53896bf272e87704826b77e82eddb3ef/zim/formats/__init__.py#L903
final int pos = markdownPathToImage.indexOf("?");
if (pos != -1) {
final String image = markdownPathToImage.substring(0, pos);
final String[] options = markdownPathToImage.substring(pos + 1).split("&");
String link = null; // <a href="link"></a> or [![name](image)](link)
StringBuilder attributes = new StringBuilder(); // <img id="" width="" height="" />
// The 'type' tag is for backward compatibility of image generators before
// Zim version 0.70. Here, it probably may be ignored:
// https://github.com/zim-desktop-wiki/zim-desktop-wiki/blob/c88cf3cb53896bf272e87704826b77e82eddb3ef/zim/formats/wiki.py#586
final Pattern tags = Pattern.compile("(id|width|height|href)=(.+)", Pattern.CASE_INSENSITIVE);
for (String item : options) {
final Matcher data = tags.matcher(item);
if (data.matches()) {
final String key = Objects.requireNonNull(data.group(1)).toLowerCase();
String value = data.group(2);
try {
value = URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (key.equals("href")) {
link = value;
} else {
attributes.append(String.format("%s=\"%s\" ", key, value));
}
}
}
String html = String.format("<img src=\"%s\" alt=\"%s\" %s/>", image, currentPageFileName, attributes);
if (link != null) {
AppSettings settings = ApplicationObject.settings();
File notebookDir = settings.getNotebookDirectory();
link = WikitextLinkResolver.resolveAttachmentPath(link, notebookDir, file, settings.isWikitextDynamicNotebookRootEnabled());
html = String.format("<a href=\"%s\">%s</a>", link, html);
}
return html;
}

return String.format("![%s](%s)", currentPageFileName, markdownPathToImage);
}

@Override
Expand Down
Loading
Loading