Skip to content

fix(client): don't break LaTeX when replacing carriage returns #923

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

Merged
merged 1 commit into from
Dec 16, 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
8 changes: 8 additions & 0 deletions src/client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,11 @@ export const CASE_SENSITIVE_TAG_NAMES_MAP = CASE_SENSITIVE_TAG_NAMES.reduce(
},
{} as Record<string, string>,
);

export const CARRIAGE_RETURN = '\r';
export const CARRIAGE_RETURN_REGEX = new RegExp(CARRIAGE_RETURN, 'g');
export const CARRIAGE_RETURN_PLACEHOLDER = `__HTML_DOM_PARSER_CARRIAGE_RETURN_PLACEHOLDER_${Date.now()}__`;
export const CARRIAGE_RETURN_PLACEHOLDER_REGEX = new RegExp(
CARRIAGE_RETURN_PLACEHOLDER,
'g',
);
12 changes: 9 additions & 3 deletions src/client/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Comment, Element, ProcessingInstruction, Text } from 'domhandler';

import type { DOMNode } from '../types';
import { CASE_SENSITIVE_TAG_NAMES_MAP } from './constants';
import {
CARRIAGE_RETURN,
CARRIAGE_RETURN_PLACEHOLDER,
CARRIAGE_RETURN_PLACEHOLDER_REGEX,
CARRIAGE_RETURN_REGEX,
CASE_SENSITIVE_TAG_NAMES_MAP,
} from './constants';

/**
* Gets case-sensitive tag name.
Expand Down Expand Up @@ -58,7 +64,7 @@ function formatTagName(tagName: string): string {
* @returns - HTML string with escaped special characters.
*/
export function escapeSpecialCharacters(html: string): string {
return html.replace(/\r/g, '\\r');
return html.replace(CARRIAGE_RETURN_REGEX, CARRIAGE_RETURN_PLACEHOLDER);
}

/**
Expand All @@ -68,7 +74,7 @@ export function escapeSpecialCharacters(html: string): string {
* @returns - Text with escaped characters reverted.
*/
export function revertEscapedCharacters(text: string): string {
return text.replace(/\\r/g, '\r');
return text.replace(CARRIAGE_RETURN_PLACEHOLDER_REGEX, CARRIAGE_RETURN);
}

/**
Expand Down
10 changes: 10 additions & 0 deletions test/cases/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,16 @@ module.exports = [
data: '<div>Hello\r<span>Beautiful\r</span>World</div>',
},

// LaTeX
{
name: 'LaTeX',
data: '<span class="math">\\left(\\right)\\rD\\rightarrow\\reals\\ni</span>',
},
{
name: 'LaTeX with carriage return',
data: '<span class="math">\\left(\\right)\\rD\\rightarrow\\reals\\ni</span>\r\n',
},

// custom tag
{
name: 'custom tag',
Expand Down
2 changes: 2 additions & 0 deletions test/cases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ module.exports = {
html,
svg,
};

module.exports.default = module.exports;
57 changes: 33 additions & 24 deletions test/server/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from 'chai';

import { CARRIAGE_RETURN_PLACEHOLDER } from '../../src/client/constants';
import { formatDOM } from '../../src/client/utilities';
import { revertEscapedCharacters } from '../../src/client/utilities';
import { escapeSpecialCharacters } from '../../src/client/utilities';
Expand All @@ -15,59 +16,67 @@ describe('client utilities', () => {

describe('escapeSpecialCharacters', () => {
it('escapes carriage return characters', () => {
const input = 'Hello\rWorld';
const expected = 'Hello\\rWorld';
expect(escapeSpecialCharacters(input)).to.equal(expected);
expect(escapeSpecialCharacters('Hello\rWorld')).to.equal(
`Hello${CARRIAGE_RETURN_PLACEHOLDER}World`,
);
});

it('does not modify strings without special characters', () => {
const input = 'Hello World';
expect(escapeSpecialCharacters(input)).to.equal(input);
const text = 'Hello World';
expect(escapeSpecialCharacters(text)).to.equal(text);
});

it('handles empty strings', () => {
expect(escapeSpecialCharacters('')).to.equal('');
const text = '';
expect(escapeSpecialCharacters(text)).to.equal(text);
});

it('handles multiple carriage returns', () => {
const input = 'Hello\rDear\rWorld';
const expected = 'Hello\\rDear\\rWorld';
expect(escapeSpecialCharacters(input)).to.equal(expected);
expect(escapeSpecialCharacters('Hello\rDear\rWorld')).to.equal(
`Hello${CARRIAGE_RETURN_PLACEHOLDER}Dear${CARRIAGE_RETURN_PLACEHOLDER}World`,
);
});

it('only escapes carriage returns', () => {
const input = 'Hello\rWorld\n'; // \n should not be affected
const expected = 'Hello\\rWorld\n';
expect(escapeSpecialCharacters(input)).to.equal(expected);
// `\n` and `\right` should not be affected
expect(escapeSpecialCharacters('Hello\rWorld\n\right')).to.equal(
`Hello${CARRIAGE_RETURN_PLACEHOLDER}World\n${CARRIAGE_RETURN_PLACEHOLDER}ight`,
);
});
});

describe('revertEscapedCharacters', () => {
it('reverts escaped carriage return characters', () => {
const input = 'Hello\\rWorld';
const expected = 'Hello\rWorld';
expect(revertEscapedCharacters(input)).to.equal(expected);
expect(
revertEscapedCharacters(`Hello${CARRIAGE_RETURN_PLACEHOLDER}World`),
).to.equal('Hello\rWorld');
});

it('does not modify strings without escaped characters', () => {
const input = 'Hello World';
expect(revertEscapedCharacters(input)).to.equal(input);
const text = 'Hello World';
expect(revertEscapedCharacters(text)).to.equal(text);
});

it('handles empty strings', () => {
expect(revertEscapedCharacters('')).to.equal('');
const text = '';
expect(revertEscapedCharacters(text)).to.equal(text);
});

it('handles multiple escaped carriage returns', () => {
const input = 'Hello\\rDear\\rWorld';
const expected = 'Hello\rDear\rWorld';
expect(revertEscapedCharacters(input)).to.equal(expected);
expect(
revertEscapedCharacters(
`Hello${CARRIAGE_RETURN_PLACEHOLDER}Dear${CARRIAGE_RETURN_PLACEHOLDER}World`,
),
).to.equal('Hello\rDear\rWorld');
});

it('only reverts escaped carriage returns', () => {
const input = 'Hello\\rWorld\\n'; // \n should not be affected
const expected = 'Hello\rWorld\\n';
expect(revertEscapedCharacters(input)).to.equal(expected);
// `\n` and `\right` should not be affected
expect(
revertEscapedCharacters(
`Hello${CARRIAGE_RETURN_PLACEHOLDER}World\\n\\right`,
),
).to.equal('Hello\rWorld\\n\\right');
});
});
});
Loading