├── logos
├── logo-box-builtby.png
└── logo-box-madefor.png
├── .gitignore
├── CHANGELOG.md
├── package.json
├── index.js
├── README.md
└── test
└── test.js
/logos/logo-box-builtby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apostrophecms/absolution/main/logos/logo-box-builtby.png
--------------------------------------------------------------------------------
/logos/logo-box-madefor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apostrophecms/absolution/main/logos/logo-box-madefor.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | *.DS_Store
3 | node_modules
4 | # We do not commit CSS, only LESS
5 | public/css/*.css
6 | package-lock.json
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.1.0 (2024-01-25)
2 |
3 | * Added a fix for srcset in img tag. Thanks to [Gauav Kumar](https://github.com/gkumar9891) for this contribution!
4 |
5 | ## 1.0.4 (2023-05-26)
6 |
7 | * Add test and documentation about adding custom self-closing tags.
8 |
9 | ## 1.0.3 (2023-02-13)
10 |
11 | * Although attributes are already escaped, we still have to output them
12 | using quotation marks that are compatible with the escaped values, e.g.
13 | if the escaped values contain unescaped double-quotes, then we must
14 | single-quote the attribute, and vice versa. Note that it is not the task
15 | of `absolution` to verify that the escapes are valid overall, only to
16 | do no harm when transforming the document's URLs.
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "absolution",
3 | "version": "1.1.0",
4 | "description": "absolution accepts HTML and a base URL, and returns HTML with absolute URLs. Great for generating valid RSS feeds.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/apostrophecms/absolution.git"
12 | },
13 | "keywords": [
14 | "url",
15 | "urls",
16 | "resolver",
17 | "url",
18 | "resolver",
19 | "html",
20 | "resolve",
21 | "urls",
22 | "in",
23 | "html",
24 | "rss",
25 | "feed"
26 | ],
27 | "author": "Apostrophe Technologies",
28 | "license": "MIT",
29 | "readmeFilename": "README.md",
30 | "dependencies": {
31 | "htmlparser2": "~3.3.0",
32 | "lodash": "~4.17.15"
33 | },
34 | "devDependencies": {
35 | "mocha": "^7.2.0"
36 | }
37 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var htmlparser = require('htmlparser2');
2 | var _ = require('lodash');
3 | var url = require('url');
4 |
5 | var absolution = module.exports = function(input, base, options) {
6 | if (!options) {
7 | options = absolution.defaults;
8 | } else {
9 | _.defaults(options, absolution.defaults);
10 | }
11 |
12 | var selfClosingMap = {};
13 | _.forEach(options.selfClosing, function(tag) {
14 | selfClosingMap[tag] = true;
15 | });
16 |
17 | var result = '';
18 |
19 | var parser = new htmlparser.Parser({
20 | onopentag: function(name, attribs) {
21 | _.forEach(options.urlAttributes, function(attr) {
22 | if (_.has(attribs, attr) && attribs[attr].trim()) {
23 | if (attr === 'srcset') {
24 | let strings = _.split(attribs[attr], ",");
25 |
26 | _.forEach(strings, function(str, index) {
27 | str = str.trim();
28 | strings[index] = _.replace(str, _.split(str, " ")[0], url.resolve(base, _.split(str, " ")[0]))
29 | })
30 |
31 | strings = strings.join(", ");
32 | attribs[attr] = strings;
33 |
34 | } else {
35 | attribs[attr] = url.resolve(base, attribs[attr]);
36 | }
37 |
38 | if (options.decorator) {
39 | attribs[attr] = options.decorator(attribs[attr]);
40 | }
41 | }
42 | });
43 | result += '<' + name;
44 | _.forEach(attribs, function(value, a) {
45 | result += ' ' + a;
46 | if (value.length) {
47 | // Values are ALREADY escaped, calling escapeHtml here
48 | // results in double escapes
49 | if (value.includes('"')) {
50 | // Since htmlparser2 only gives us back valid attributes,
51 | // we can assume any value with double quotes should be a
52 | // single-quoted attribute
53 | result += "='" + value + "'";
54 | } else {
55 | result += '="' + value + '"';
56 | }
57 | }
58 | });
59 | if (_.has(selfClosingMap, name)) {
60 | result += " />";
61 | } else {
62 | result += ">";
63 | }
64 | },
65 | ontext: function(text) {
66 | // It is NOT actually raw text, entities are already escaped.
67 | // If we call escapeHtml here we wind up double-escaping.
68 | result += text;
69 | },
70 | onclosetag: function(name) {
71 | if (_.has(selfClosingMap, name)) {
72 | // Already output />
73 | return;
74 | }
75 | result += "" + name + ">";
76 | }
77 | });
78 | parser.write(input);
79 | parser.end();
80 | return result;
81 | };
82 |
83 | absolution.defaults = {
84 | urlAttributes: [ 'href', 'src', 'action', 'srcset' ],
85 | selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ]
86 | };
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # absolution
2 |
3 |
4 |
5 | `absolution` accepts HTML and a base URL, and returns HTML with absolute URLs. Great for generating valid RSS feeds.
6 |
7 | `absolution` is not too picky about your HTML.
8 |
9 | ## Requirements
10 |
11 | `absolution` is intended for use with Node. That's pretty much it. All of its npm dependencies are pure JavaScript. `absolution` is built on the excellent `htmlparser2` module.
12 |
13 | ## How to use
14 |
15 | `npm install absolution`
16 |
17 | ```javascript
18 | var absolution = require('absolution');
19 |
20 | var dirty = 'Foo!';
21 | var clean = absolution(dirty, 'http://example.com');
22 |
23 | // clean is now:
24 | // Foo!
25 | ```
26 |
27 | Boom!
28 |
29 | If you want to do further processing of each absolute URL, you can also pass a decorator function:
30 |
31 | ```javascript
32 | var clean = absolution(dirty, 'http://example.com', {
33 | decorator: function(url) {
34 | return 'http://mycoolthing.com?url=' + encodeURIComponent(url);
35 | }
36 | });
37 | ```
38 |
39 | ## Having issues with SVG markup?
40 |
41 | > How can I keep SVG self-closing tags intact?
42 |
43 | You can add custom self-closing tags via the `selfClosing` option:
44 |
45 | ```javascript
46 | var absolution = require('absolution');
47 |
48 | var dirty = `
49 |
56 | `;
57 | var clean = absolution(dirty, 'http://example.com', {
58 | selfClosing: [
59 | // keep default `selfClosing` tags:
60 | ...absolution.defaults.selfClosing,
61 |
62 | // add custom tags:
63 | 'path',
64 | 'circle'
65 | ]
66 | });
67 |
68 | // clean is now:
69 | //
76 | ```
77 |
78 | ## Changelog
79 |
80 | 1.0.2: Updates to lodash v4 and mocha v7 for security vulnerability fixes. Also update package metadata.
81 |
82 | 1.0.0: no new changes; declared stable as with the addition of the decorator option there's little left to do, and all tests are passing nicely.
83 |
84 | 0.2.0: decorator option added.
85 |
86 | 0.1.0: initial release.
87 |
88 | ## About P'unk Avenue and Apostrophe
89 |
90 | `absolution` was created at [P'unk Avenue](http://punkave.com) for use in Apostrophe, an open-source content management system built on node.js. If you like `absolution` you should definitely [check out apostrophenow.org](http://apostrophenow.org). Also be sure to visit us on [github](http://github.com/punkave).
91 |
92 | ## Support
93 |
94 | Feel free to open issues on [github](http://github.com/punkave/absolution).
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | var assert = require("assert");
2 | var absolution = require('../index.js');
3 |
4 | describe('absolution', function() {
5 | it('should pass through unrelated markup unaltered', function() {
6 | assert.equal(absolution('
Hello there
Hello there
Whee!
', 'http://example.com/child/'), 'Blah blah blahWhee!
'); 10 | }); 11 | it('should respect absolute URLs', function() { 12 | assert.equal(absolution('Test', 'http://example.com/child/'), 'Test'); 13 | }); 14 | it('should not bollux up empty tags', function() { 15 | assert.equal(absolution('
`, 'http://example.com/');
100 |
101 | const expected = `
`
102 |
103 | assert.equal(result, expected);
104 | })
105 |
106 | it('should handle the srcset if srcset configured correct in img', function() {
107 | const result = absolution(`
`, 'http://example.com/');
108 | const expected = `
`
109 |
110 | assert.equal(result, expected)
111 | })
112 |
113 |
114 | it('should handle the srcset in picture', function() {
115 | const picture = `
116 |
120 |
128 |
146 |
153 |