Repository: darkreader/darkreader
Pull request #523 (2018-05-22 04:00:38)

Invert math formula image in zhuanlan.zhihu.com
Invert math formula image in zhuanlan.zhihu.com
Note: Such this image is generated by url, for example:

```
<img src="https://www.zhihu.com/equation?tex=Cost+function+%3D+Loss+%28say%2C+binary%5C_+crossentropy%29+%2B+Regularization%5C_+term" alt="Cost function = Loss (say, binary\_ crossentropy) + Regularization\_ term" eeimg="1">
```
 
Tested in: https://zhuanlan.zhihu.com/p/37120298

Before:
![capture](https://user-images.githubusercontent.com/7560132/40341403-97db4f66-5db7-11e8-95ec-505bb8d54dca.PNG)
After:
![capture2](https://user-images.githubusercontent.com/7560132/40341406-9b605fdc-5db7-11e8-9d4c-ee44c638e6b5.PNG)



Patch:
diff --git a/src/config/dynamic-theme-fixes.config b/src/config/dynamic-theme-fixes.config
index 7a6d3729..a95b5480 100644
--- a/src/config/dynamic-theme-fixes.config
+++ b/src/config/dynamic-theme-fixes.config
@@ -57,7 +57,7 @@ INVERT
 reddit.com
 
 INVERT
-.iAsPXl
+#header a[href="/"] svg:nth-child(2)
 
 ================================
 
@@ -92,3 +92,10 @@ INVERT
 .align-by-text.style-scope.ytd-comment-replies-renderer
 g#youtube-red-paths
 .deemphasize.style-scope.yt-formatted-string
+
+================================
+
+zhuanlan.zhihu.com
+
+INVERT
+img[eeimg="1"]
diff --git a/src/generators/css-filter.ts b/src/generators/css-filter.ts
index dc106453..d6f2c59f 100644
--- a/src/generators/css-filter.ts
+++ b/src/generators/css-filter.ts
@@ -202,7 +202,7 @@ const inversionFixesCommands = {
 export function parseInversionFixes(text: string) {
     return parseSitesFixesConfig<InversionFix>(text, {
         commands: Object.keys(inversionFixesCommands),
-        getCommandPropName: (command) => inversionFixesCommands[command],
+        getCommandPropName: (command) => inversionFixesCommands[command] || null,
         parseCommandValue: (command, value) => {
             if (command === 'CSS') {
                 return value.trim();
diff --git a/src/generators/dynamic-theme.ts b/src/generators/dynamic-theme.ts
index 75bfc2fa..24ee4173 100644
--- a/src/generators/dynamic-theme.ts
+++ b/src/generators/dynamic-theme.ts
@@ -11,7 +11,7 @@ const dynamicThemeFixesCommands = {
 export function parseDynamicThemeFixes(text: string) {
     return parseSitesFixesConfig<DynamicThemeFix>(text, {
         commands: Object.keys(dynamicThemeFixesCommands),
-        getCommandPropName: (command) => dynamicThemeFixesCommands[command],
+        getCommandPropName: (command) => dynamicThemeFixesCommands[command] || null,
         parseCommandValue: (command, value) => parseArray(value),
     });
 }
diff --git a/src/generators/utils/parse.ts b/src/generators/utils/parse.ts
index 40a390db..1612dc8f 100644
--- a/src/generators/utils/parse.ts
+++ b/src/generators/utils/parse.ts
@@ -13,41 +13,31 @@ interface SitesFixesParserOptions {
 export function parseSitesFixesConfig<T extends SiteProps>(text: string, options: SitesFixesParserOptions) {
     const sites: T[] = [];
 
-    // Split blocks
-    const blocks = text.replace(/\r/g, '').split(/={2,}/g);
+    const blocks = text.replace(/\r/g, '').split(/^\s*={2,}\s*$/gm);
     blocks.forEach((block) => {
-
-        interface CommandLocation {
-            command: string;
-            start: number;
-            end: number;
-        }
-
-        let commandsLocations: CommandLocation[] = [];
-        options.commands.forEach((command) => {
-            let end = 0;
-            let start: number;
-            while ((start = block.indexOf(command, end)) >= 0) {
-                end = start + command.length;
-                commandsLocations.push({command, start, end});
+        const lines = block.split('\n');
+        const commandIndices: number[] = [];
+        lines.forEach((ln, i) => {
+            if (ln.match(/^\s*[A-Z]+(\s[A-Z]+)*\s*$/)) {
+                commandIndices.push(i);
             }
         });
-        commandsLocations = commandsLocations
-            .filter(({start, end}, i) => !commandsLocations.find(({start: otherStart, end: otherEnd}, otherI) => (i !== otherI && start >= otherStart && end <= otherEnd)))
-            .sort((a, b) => a.start - b.start);
 
-        if (commandsLocations.length === 0) {
+        if (commandIndices.length === 0) {
             return;
         }
 
         const siteFix = {
-            url: parseArray(block.substring(0, commandsLocations[0].start)) as string[],
+            url: parseArray(lines.slice(0, commandIndices[0]).join('\n')) as string[],
         } as T;
 
-        commandsLocations.forEach(({command, start, end}, i) => {
-            const valueEnd = i < commandsLocations.length - 1 ? commandsLocations[i + 1].start : block.length;
-            const valueText = block.substring(end, valueEnd);
+        commandIndices.forEach((commandIndex, i) => {
+            const command = lines[commandIndex].trim();
+            const valueText = lines.slice(commandIndex + 1, i === commandIndices.length - 1 ? lines.length : commandIndices[i + 1]).join('\n');
             const prop = options.getCommandPropName(command);
+            if (!prop) {
+                return;
+            }
             const value = options.parseCommandValue(command, valueText);
             siteFix[prop] = value;
         });


Test patch:
diff --git a/tests/config.tests.ts b/tests/config.tests.ts
index 9fdf83b3..85bf1b98 100644
--- a/tests/config.tests.ts
+++ b/tests/config.tests.ts
@@ -1,7 +1,7 @@
 import {readFile} from 'fs';
 import {resolve as resolvePath} from 'path';
 import {compareURLPatterns} from '../src/utils/url';
-import {parseArray, formatArray} from '../src/utils/text';
+import {parseArray, formatArray, getTextDiffIndex, getTextPositionMessage} from '../src/utils/text';
 import {parseInversionFixes, formatInversionFixes} from '../src/generators/css-filter';
 import {parseDynamicThemeFixes, formatDynamicThemeFixes} from '../src/generators/dynamic-theme';
 import {parseStaticThemes, formatStaticThemes} from '../src/generators/static-theme';
@@ -22,6 +22,15 @@ function isURLPatternValid(url: string) {
     return url.length > 0 && url.indexOf('://') < 0;
 }
 
+function throwIfDifferent(input: string, expected: string, message: string) {
+    return () => {
+        const diffIndex = getTextDiffIndex(input, expected);
+        if (diffIndex >= 0) {
+            throw new Error(`${message}\n${getTextPositionMessage(input, diffIndex)}`);
+        }
+    };
+}
+
 test('Dark Sites list', async () => {
     const file = await readConfig('dark-sites.config');
     const sites = parseArray(file);
@@ -36,7 +45,7 @@ test('Dark Sites list', async () => {
     expect(sites).toEqual(sites.slice().sort(compareURLPatterns));
 
     // sites are properly formatted
-    expect(file).toEqual(formatArray(sites));
+    expect(throwIfDifferent(file, formatArray(sites), 'Dark Sites list format error')).not.toThrow();
 });
 
 test('Dynamic Theme Fixes config', async () => {
@@ -53,7 +62,25 @@ test('Dynamic Theme Fixes config', async () => {
     expect(fixes.every(({invert}) => (invert || []).every((s) => s.indexOf(',') < 0))).toBe(true);
 
     // fixes are properly formatted
-    expect(file).toEqual(formatDynamicThemeFixes(fixes));
+    expect(throwIfDifferent(file, formatDynamicThemeFixes(fixes), 'Dynamic fixes format error')).not.toThrow();
+
+    // should parse empty config
+    expect(parseDynamicThemeFixes('')).toEqual([]);
+
+    // should skip unsupported commands
+    expect(parseDynamicThemeFixes([
+        'inbox.google.com',
+        'mail.google.com',
+        'INVERT', 'a', 'b',
+        'UNSUPPORTED', 'c', 'd',
+        '========',
+        'twitter.com',
+        'UNSUPPORTED', 'a', 'b',
+        'INVERT', 'c', 'd',
+    ].join('\r\n'))).toEqual([
+        {url: ['inbox.google.com', 'mail.google.com'], invert: ['a', 'b']},
+        {url: ['twitter.com'], invert: ['c', 'd']},
+    ])
 });
 
 test('Inversion Fixes config', async () => {
@@ -73,7 +100,7 @@ test('Inversion Fixes config', async () => {
     expect(fixes.every(({invert, noinvert, removebg}) => (invert || []).concat(noinvert || []).concat(removebg || []).every((s) => s.indexOf(',') < 0))).toBe(true);
 
     // fixes are properly formatted
-    expect(file).toEqual(formatInversionFixes(fixes));
+    expect(throwIfDifferent(file, formatInversionFixes(fixes), 'Inversion fixes format error')).not.toThrow();
 });
 
 test('Static Themes config', async () => {
@@ -96,5 +123,5 @@ test('Static Themes config', async () => {
             .every((s) => s.indexOf(',') < 0)))).toBe(true);
 
     // fixes are properly formatted
-    expect(file).toEqual(formatStaticThemes(themes));
+    expect(throwIfDifferent(file, formatStaticThemes(themes), 'Static theme format error')).not.toThrow();
 });