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

"Remove Links" affects certain anchor texts #260

Open
Servinjesus1 opened this issue May 27, 2024 · 1 comment
Open

"Remove Links" affects certain anchor texts #260

Servinjesus1 opened this issue May 27, 2024 · 1 comment
Labels
bug Something isn't working

Comments

@Servinjesus1
Copy link

Servinjesus1 commented May 27, 2024

Advanced anchor texts (i.e. figure captions) can be messed up by the current version of "Remove Links" built--in, so I wrote my own.

In particular, if you have pandoc references [@Reference] or math with vertical bars $\left|M\right|$ you may benefit from this. It essentially removes the logic to cleanse vertical bars from link anchor text, checks for newlines and skips removal if it finds a matching pair of brackets but not on one line, and skips escaped brackets.

I used ChatGPT so it also adjusted the logic which makes it hard for me to suggest a 1:1 request to update the code as it's built in.

Details

module.exports = {
  id: "remove-links",
  description: {
    name: "Remove Links",
    description: "Removes wiki and/or external links.",
    availableKinds: ["Scene", "Manuscript"],
    options: [
      {
        id: "remove-wikilinks",
        name: "Remove Wikilinks",
        description: "Remove brackets from [[wikilinks]].",
        type: Boolean,
        default: true,
      },
      {
        id: "remove-external-links",
        name: "Remove External Links",
        description: "Remove external links, leaving only the anchor text.",
        type: Boolean,
        default: true,
      },
    ],
  },
  compile(input, context) {
    const removeWikilinks = context.optionValues["remove-wikilinks"];
    const removeExternalLinks = context.optionValues[
      "remove-external-links"
    ];

    const replaceLinks = (contents) => {
      if (removeWikilinks) {
        contents = replaceWikiLinks(contents);
      }
      if (removeExternalLinks) {
        contents = replaceExternalLinks(contents);
      }

      return contents;
    };

    if (context.kind === "Scene") {
      return (input).map((sceneInput) => {
        const contents = replaceLinks(sceneInput.contents);
        return {
          ...sceneInput,
          contents,
        };
      });
    } else {
      return {
        ...(input),
        contents: replaceLinks((input).contents),
      };
    }
  },
};

function replaceWikiLinks(contents) {
    let startOfAlias = -1;
    let additionalAlias = false;
    let end = -1;

    // moving backward allows us to replace within the loop,
    // so no additional memory.
    for (let i = contents.length - 1; i >= 0; i--) {
        const char = contents.charAt(i);
        if (end < 0) {
            if (char === "]" && i > 0 && contents.charAt(i - 1) === "]") {
                end = i - 1;
                i -= 1;
            }
        } else if (char === "|") {
            if (startOfAlias < 0 && !additionalAlias) {
                startOfAlias = i + 1;
            }
            continue;
        } else if (char === "[" && i > 0 && contents.charAt(i - 1) === "[") {
            if (i > 1 && contents.charAt(i - 2) === "!") {
                i -= 2;
            } else if (startOfAlias >= 0) {
                let replacement = contents.slice(startOfAlias, end);
                contents = contents.slice(0, i - 1) + replacement + contents.slice(end + 2);
                startOfAlias = -1;
                end = -1;
                additionalAlias = false;
            }
        } else if (char === "\n") {
            startOfAlias = -1;
            end = -1;
            additionalAlias = false;
        }
    }
    return contents;
}

function replaceExternalLinks(contents) {
  let end = -1;
  let aliasEnd = -1;
  let depth = 0;

  // moving backward allows us to replace within the loop,
  // so no additional memory.
  for (let i = contents.length - 1; i >= 0; i--) {
    const char = contents.charAt(i);
    if (end < 0) {
      if (char === ")") {
        end = i;
      }
    } else {
      if (aliasEnd < 0) {
        if (char === "(") {
          if (i > 0 && contents.charAt(i - 1) === "]") {
            aliasEnd = i - 1;
          } else {
            // invalid link
            end = -1;
            aliasEnd = -1;
          }
          // can skip the next character
          i = i - 1;
          continue;
        }
      } 
      
      if (char === "]") {
        depth += 1;
        continue;
      }

      if (char === "[") {
        if (depth == 0) {
          if (i > 0 && contents.charAt(i - 1) === "!") {
            // embed, jump to i - 1
            i -= 1;
          } else {
            const replacement = contents.slice(i + 1, aliasEnd).replace("\\", "");
            contents = contents.slice(0, i) + replacement + contents.slice(end + 1);
          }
          end = -1;
          aliasEnd = -1;
        } else {
          depth -= 1;
        }
        continue;
      }
    }
  }

  return contents;
}

@Servinjesus1 Servinjesus1 added the bug Something isn't working label May 27, 2024
@Servinjesus1
Copy link
Author

Servinjesus1 commented May 28, 2024

Edit to above code: added depth logic to count brackets. A simple workaround (escaping brackets) didn't work with pandoc-fignos, so I had to add logic that counts depth and only does replacement at zero depth (the actual caption and not inside some bracketed span in the caption) which is the better solution anyway.

Note to other pandoc users: Do not escape brackets inside captions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant