Full Code of mixmark-io/turndown for AI

master 0a6d298a20bd cached
28 files
90.9 KB
26.6k tokens
44 symbols
1 requests
Download .txt
Repository: mixmark-io/turndown
Branch: master
Commit: 0a6d298a20bd
Files: 28
Total size: 90.9 KB

Directory structure:
gitextract_kkzqji9c/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .tm_properties
├── LICENSE
├── README.md
├── SECURITY.md
├── config/
│   ├── rollup.config.browser.cjs.mjs
│   ├── rollup.config.browser.es.mjs
│   ├── rollup.config.browser.umd.mjs
│   ├── rollup.config.cjs.mjs
│   ├── rollup.config.es.mjs
│   ├── rollup.config.iife.mjs
│   ├── rollup.config.mjs
│   ├── rollup.config.test.mjs
│   └── rollup.config.umd.mjs
├── index.html
├── package.json
├── src/
│   ├── collapse-whitespace.js
│   ├── commonmark-rules.js
│   ├── html-parser.js
│   ├── node.js
│   ├── root-node.js
│   ├── rules.js
│   ├── turndown.js
│   └── utilities.js
└── test/
    ├── index.html
    ├── internals-test.js
    └── turndown-test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on: 
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node_version: [18, 20, 21]

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node_version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node_version }}
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test


================================================
FILE: .gitignore
================================================
dist
lib
node_modules
npm-debug.log
test/*browser.js


================================================
FILE: .tm_properties
================================================
[test/index.html]
scopeAttributes = attr.keep-whitespace


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Dom Christie

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Turndown

Convert HTML into Markdown with JavaScript.

See it in action [online](https://mixmark-io.github.io/turndown/).

## Project Updates
* `to-markdown` has been renamed to Turndown. See the [migration guide](https://github.com/domchristie/to-markdown/wiki/Migrating-from-to-markdown-to-Turndown) for details.
* Turndown repository has changed its URL to https://github.com/mixmark-io/turndown.

## Installation

npm:

```
npm install turndown
```

Browser:

```html
<script src="https://unpkg.com/turndown/dist/turndown.js"></script>
```

For usage with RequireJS, UMD versions are located in `lib/turndown.umd.js` (for Node.js) and `lib/turndown.browser.umd.js` for browser usage. These files are generated when the npm package is published. To generate them manually, clone this repo and run `npm run build`.

## Usage

```js
// For Node.js
var TurndownService = require('turndown')

var turndownService = new TurndownService()
var markdown = turndownService.turndown('<h1>Hello world!</h1>')
```

Turndown also accepts DOM nodes as input (either element nodes, document nodes,  or document fragment nodes):

```js
var markdown = turndownService.turndown(document.getElementById('content'))
```

## Options

Options can be passed in to the constructor on instantiation. For example:

```js
var turndownService = new TurndownService({ option: 'value' })
```

| Option                | Valid values  | Default |
| :-------------------- | :------------ | :------ |
| `headingStyle`        | `setext` or `atx` | `setext`  |
| `hr`                  | Any [Thematic break](http://spec.commonmark.org/0.27/#thematic-breaks) | `* * *` |
| `bulletListMarker`    | `-`, `+`, or `*` | `*` |
| `codeBlockStyle`      | `indented` or `fenced` | `indented` |
| `fence`               | ` ``` ` or `~~~` | ` ``` ` |
| `emDelimiter`         | `_` or `*` | `_` |
| `strongDelimiter`     | `**` or `__` | `**` |
| `linkStyle`           | `inlined` or `referenced` | `inlined` |
| `linkReferenceStyle`  | `full`, `collapsed`, or `shortcut` | `full` |
| `preformattedCode`    | `false` or [`true`](https://github.com/lucthev/collapse-whitespace/issues/16) | `false` |

### Advanced Options

| Option                | Valid values  | Default |
| :-------------------- | :------------ | :------ |
| `blankReplacement`    | rule replacement function | See **Special Rules** below |
| `keepReplacement`     | rule replacement function | See **Special Rules** below |
| `defaultReplacement`  | rule replacement function | See **Special Rules** below |

## Methods

### `addRule(key, rule)`

The `key` parameter is a unique name for the rule for easy reference. Example:

```js
turndownService.addRule('strikethrough', {
  filter: ['del', 's', 'strike'],
  replacement: function (content) {
    return '~' + content + '~'
  }
})
```

`addRule` returns the `TurndownService` instance for chaining.

See **Extending with Rules** below.

### `keep(filter)`

Determines which elements are to be kept and rendered as HTML. By default, Turndown does not keep any elements. The filter parameter works like a rule filter (see section on filters belows). Example:

```js
turndownService.keep(['del', 'ins'])
turndownService.turndown('<p>Hello <del>world</del><ins>World</ins></p>') // 'Hello <del>world</del><ins>World</ins>'
```

This will render `<del>` and `<ins>` elements as HTML when converted.

`keep` can be called multiple times, with the newly added keep filters taking precedence over older ones. Keep filters will be overridden by the standard CommonMark rules and any added rules. To keep elements that are normally handled by those rules, add a rule with the desired behaviour.

`keep` returns the `TurndownService` instance for chaining.

### `remove(filter)`

Determines which elements are to be removed altogether i.e. converted to an empty string. By default, Turndown does not remove any elements. The filter parameter works like a rule filter (see section on filters belows). Example:

```js
turndownService.remove('del')
turndownService.turndown('<p>Hello <del>world</del><ins>World</ins></p>') // 'Hello World'
```

This will remove `<del>` elements (and contents).

`remove` can be called multiple times, with the newly added remove filters taking precedence over older ones. Remove filters will be overridden by the keep filters,  standard CommonMark rules, and any added rules. To remove elements that are normally handled by those rules, add a rule with the desired behaviour.

`remove` returns the `TurndownService` instance for chaining.

### `use(plugin|array)`

Use a plugin, or an array of plugins. Example:

```js
// Import plugins from turndown-plugin-gfm
var turndownPluginGfm = require('turndown-plugin-gfm')
var gfm = turndownPluginGfm.gfm
var tables = turndownPluginGfm.tables
var strikethrough = turndownPluginGfm.strikethrough

// Use the gfm plugin
turndownService.use(gfm)

// Use the table and strikethrough plugins only
turndownService.use([tables, strikethrough])
```

`use` returns the `TurndownService` instance for chaining.

See **Plugins** below.

## Extending with Rules

Turndown can be extended by adding **rules**. A rule is a plain JavaScript object with `filter` and `replacement` properties. For example, the rule for converting `<p>` elements is as follows:

```js
{
  filter: 'p',
  replacement: function (content) {
    return '\n\n' + content + '\n\n'
  }
}
```

The filter selects `<p>` elements, and the replacement function returns the `<p>` contents separated by two new lines.

### `filter` String|Array|Function

The filter property determines whether or not an element should be replaced with the rule's `replacement`. DOM nodes can be selected simply using a tag name or an array of tag names:

 * `filter: 'p'` will select `<p>` elements
 * `filter: ['em', 'i']` will select `<em>` or `<i>` elements

The tag names in the `filter` property are expected in lowercase, regardless of their form in the document.

Alternatively, the filter can be a function that returns a boolean depending on whether a given node should be replaced. The function is passed a DOM node as well as the `TurndownService` options. For example, the following rule selects `<a>` elements (with an `href`) when the `linkStyle` option is `inlined`:

```js
filter: function (node, options) {
  return (
    options.linkStyle === 'inlined' &&
    node.nodeName === 'A' &&
    node.getAttribute('href')
  )
}
```

### `replacement` Function

The replacement function determines how an element should be converted. It should return the Markdown string for a given node. The function is passed the node's content, the node itself, and the `TurndownService` options.

The following rule shows how `<em>` elements are converted:

```js
rules.emphasis = {
  filter: ['em', 'i'],

  replacement: function (content, node, options) {
    return options.emDelimiter + content + options.emDelimiter
  }
}
```

### Special Rules

**Blank rule** determines how to handle blank elements. It overrides every rule (even those added via `addRule`). A node is blank if it only contains whitespace, and it's not an `<a>`, `<td>`,`<th>` or a void element. Its behaviour can be customised using the `blankReplacement` option.

**Keep rules** determine how to handle the elements that should not be converted, i.e. rendered as HTML in the Markdown output. By default, no elements are kept. Block-level elements will be separated from surrounding content by blank lines. Its behaviour can be customised using the `keepReplacement` option.

**Remove rules** determine which elements to remove altogether. By default, no elements are removed.

**Default rule** handles nodes which are not recognised by any other rule. By default, it outputs the node's text content (separated  by blank lines if it is a block-level element). Its behaviour can be customised with the `defaultReplacement` option.

### Rule Precedence

Turndown iterates over the set of rules, and picks the first one that matches the `filter`. The following list describes the order of precedence:

1. Blank rule
2. Added rules (optional)
3. Commonmark rules
4. Keep rules
5. Remove rules
6. Default rule

## Plugins

The plugin API provides a convenient way for developers to apply multiple extensions. A plugin is just a function that is called with the `TurndownService` instance.

## Escaping Markdown Characters

Turndown uses backslashes (`\`) to escape Markdown characters in the HTML input. This ensures that these characters are not interpreted as Markdown when the output is compiled back to HTML. For example, the contents of `<h1>1. Hello world</h1>` needs to be escaped to `1\. Hello world`, otherwise it will be interpreted as a list item rather than a heading.

To avoid the complexity and the performance implications of parsing the content of every HTML element as Markdown, Turndown uses a group of regular expressions to escape potential Markdown syntax. As a result, the escaping rules can be quite aggressive.

### Overriding `TurndownService.prototype.escape`

If you are confident in doing so, you may want to customise the escaping behaviour to suit your needs. This can be done by overriding `TurndownService.prototype.escape`. `escape` takes the text of each HTML element and should return a version with the Markdown characters escaped.

Note: text in code elements is never passed to`escape`.

## License

turndown is copyright © 2017+ Dom Christie and released under the MIT license.


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

| Version | Supported          | Remark |
| ------- | ------------------ | -------|
| 7.0.x   | :white_check_mark: | |
| < 7.0   | :x:                | jsdom |

## DOM Parser Notice

Turndown input is
* either a string that is passed to a DOM parser
* or an `HTMLElement` referring to an already built DOM tree

When a string input is passed, the DOM parser is picked as follows.
* For web browser usage, the corresponding native web parser is used, which is typically `DOMImplementation`.
* For standalone usage, custom [domino](https://github.com/mixmark-io/domino) parser is used.

Please note that a malicious string input can cause undesired effects within the DOM parser
even before Turndown code starts processing the document itself.
These effects especially include script execution and downloading external resources.

For critical applications with untrusted inputs, you should consider either cleaning up 
the input with a dedicated HTML sanitizer library or using an alternate DOM parser that
better suits your security needs.

In particular, Turndown version 6 and below used [jsdom](https://github.com/jsdom/jsdom) as the
standalone DOM parser. As `jsdom` is a fully featured DOM parser with script execution support,
it imposes an inherent security risk. We recommend upgrading to version 7, which uses custom
[domino](https://github.com/mixmark-io/domino) that doesn't even support executing scripts nor
downloading external resources.

## Reporting a Vulnerability

If you've found a vulnerability, please report it to disclosure@orchitech.cz and we'll get back to you.


================================================
FILE: config/rollup.config.browser.cjs.mjs
================================================
import config from './rollup.config.mjs'

export default config({
  output: {
    file: 'lib/turndown.browser.cjs.js',
    format: 'cjs',
    exports: 'auto'
  },
  browser: true
})


================================================
FILE: config/rollup.config.browser.es.mjs
================================================
import config from './rollup.config.mjs'

export default config({
  output: {
    file: 'lib/turndown.browser.es.js',
    format: 'es'
  },
  browser: true
})


================================================
FILE: config/rollup.config.browser.umd.mjs
================================================
import config from './rollup.config.mjs'

export default config({
  output: {
    file: 'lib/turndown.browser.umd.js',
    format: 'umd',
    name: 'TurndownService'
  },
  browser: true
})


================================================
FILE: config/rollup.config.cjs.mjs
================================================
import config from './rollup.config.mjs'

export default config({
  output: {
    file: 'lib/turndown.cjs.js',
    format: 'cjs',
    exports: 'auto'
  },
  browser: false
})


================================================
FILE: config/rollup.config.es.mjs
================================================
import config from './rollup.config.mjs'

export default config({
  output: {
    file: 'lib/turndown.es.js',
    format: 'es'
  },
  browser: false
})


================================================
FILE: config/rollup.config.iife.mjs
================================================
import config from './rollup.config.mjs'

export default config({
  output: {
    file: 'dist/turndown.js',
    format: 'iife',
    name: 'TurndownService'
  },
  browser: true
})


================================================
FILE: config/rollup.config.mjs
================================================
import commonjs from '@rollup/plugin-commonjs'
import replace from '@rollup/plugin-replace'
import resolve from '@rollup/plugin-node-resolve'
import babel from '@rollup/plugin-babel'

export default function (config) {
  return {
    input: 'src/turndown.js',
    output: config.output,
    external: ['@mixmark-io/domino'],
    plugins: [
      commonjs(),
      replace({ 'process.browser': JSON.stringify(!!config.browser), preventAssignment: true }),
      resolve(),
      babel({
        babelHelpers: 'bundled',
        plugins: [
          '@babel/plugin-transform-block-scoping',
          '@babel/plugin-transform-shorthand-properties'
        ]
      })
    ]
  }
}


================================================
FILE: config/rollup.config.test.mjs
================================================
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'

export default {
  input: 'test/turndown-test.js',
  output: {
    file: 'test/turndown-test.browser.js',
    format: 'iife',
    name: 'TurndownTest'
  },
  plugins: [
    resolve({ browser: true }),
    commonjs({
      ignore: ['events', 'fs', 'path', 'stream']
    })
  ]
}


================================================
FILE: config/rollup.config.umd.mjs
================================================
import config from './rollup.config.mjs'

export default config({
  output: {
    file: 'lib/turndown.umd.js',
    format: 'umd',
    name: 'TurndownService'
  }
})


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Turndown Demo</title>
<meta name="viewport" content="width=device-width">
<style>
  * {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
  }

  body {
    margin: 0 auto;
    font-family: courier, monospace, sans-serif;
    line-height: 1.3125;
    color: #333;
  }

  header {
    padding: 0.5em;
    background-color: #fff;
    text-align: center;
  }

  footer {
    font-size: 0.875em;
    text-align: center;
    color: #666;
  }

  h1,
  label[for="input"],
  label[for="output"] {
    margin: 0;
    font-size: 1em;
    font-weight: 700;
    letter-spacing: 0.0625em;
    text-transform: uppercase;
  }

  label[for="input"],
  label[for="output"] {
    font-weight: 400;
  }

  .source-link {
    font-size: 0.875em;
  }

  .row:before,
  .row:after {
    content: " ";
    display: table;
  }

  .row:after {
    clear: both;
  }

  .row {
    *zoom: 1;
  }

  .col,
  .form-group {
    padding: 0 0.5em;
  }

  @media (min-width: 36em) {
    .col {
      float: left;
      width: 50%;
    }
  }

  textarea {
    width: 100%;
    height: 36em;
    margin: 0;
    padding: 0.5em;
    overflow: auto;
    border: 1px solid;
    background-color: #fff;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: inherit;
  }

  #input {
    background-color: #333;
    color: #fff;
    border: none;
  }

  select {
    display: block;
    width: 100%;
    font-size: inherit;
  }

  .form-group {
    display: inline-block;
  }

  .form-group label {
    font-size: 0.875em;
  }
</style>
<script src="https://unpkg.com/turndown/dist/turndown.js"></script>
</head>
<body>
<header>
  <h1>turndown</h1>
  <a class="source-link" href="https://github.com/mixmark-io/turndown">Source on GitHub</a>
</header>
<div class="row">
  <div class="col">
    <label for="input">HTML</label>
    <textarea cols="100" rows=10 id="input"><h1>Turndown Demo</h1>

<p>This demonstrates <a href="https://github.com/mixmark-io/turndown">turndown</a> – an HTML to Markdown converter in JavaScript.</p>

<h2>Usage</h2>

<pre><code class="language-js">var turndownService = new TurndownService()
console.log(
  turndownService.turndown('&amp;lt;h1&amp;gt;Hello world&amp;lt;/h1&amp;gt;')
)</code></pre>

<hr />

<p>It aims to be <a href="http://commonmark.org/">CommonMark</a>
 compliant, and includes options to style the output. These options include:</p>

<ul>
  <li>headingStyle (setext or atx)</li>
  <li>horizontalRule (*, -, or _)</li>
  <li>bullet (*, -, or +)</li>
  <li>codeBlockStyle (indented or fenced)</li>
  <li>fence (` or ~)</li>
  <li>emDelimiter (_ or *)</li>
  <li>strongDelimiter (** or __)</li>
  <li>linkStyle (inlined or referenced)</li>
  <li>linkReferenceStyle (full, collapsed, or shortcut)</li>
</ul></textarea>
  </div>
  <div class="col">
    <label for="output">Markdown</label>
    <textarea readonly cols="100" rows=10 id="output"></textarea>
  </div>
</div>

<div class="row">
  <form method="get" action="/turndown" id="options">
    <div class="form-group">
      <label for="headingStyle">Heading style</label>
      <select name="headingStyle" id="headingStyle">
        <option value="setext">setext</option>
        <option value="atx">atx</option>
      </select>
    </div>

    <div class="form-group">
      <label for="hr">Horizontal rule</label>
      <select name="hr" id="hr">
        <option value="* * *">* * *</option>
        <option value="- - -">- - -</option>
        <option value="_ _ _">_ _ _</option>
      </select>
    </div>

    <div class="form-group">
      <label for="bulletListMarker">Bullet</label>
      <select name="bulletListMarker" id="bulletListMarker">
        <option value="*">*</option>
        <option value="-">-</option>
        <option value="+">+</option>
      </select>
    </div>

    <div class="form-group">
      <label for="codeBlockStyle">Code block style</label>
      <select name="codeBlockStyle" id="codeBlockStyle">
        <option value="indented">indented</option>
        <option value="fenced">fenced</option>
      </select>
    </div>

    <div class="form-group">
      <label for="fence">Fence</label>
      <select name="fence" id="fence">
        <option value="```">```</option>
        <option value="~~~">~~~</option>
      </select>
    </div>

    <div class="form-group">
      <label for="emDelimiter">Em delimiter</label>
      <select name="emDelimiter" id="emDelimiter">
        <option value="_">_</option>
        <option value="*">*</option>
      </select>
    </div>

    <div class="form-group">
      <label for="strongDelimiter">Strong delimiter</label>
      <select name="strongDelimiter" id="strongDelimiter">
        <option value="**">**</option>
        <option value="__">__</option>
      </select>
    </div>

    <div class="form-group">
      <label for="linkStyle">Link style</label>
      <select name="linkStyle" id="linkStyle">
        <option value="inlined">inlined</option>
        <option value="referenced">referenced</option>
      </select>
    </div>

    <div class="form-group">
      <label for="linkReferenceStyle">Link reference style</label>
      <select name="linkReferenceStyle" id="linkReferenceStyle">
        <option value="full">full</option>
        <option value="collapsed">collapsed</option>
        <option value="shortcut">shortcut</option>
      </select>
    </div>
  </form>
</div>

<footer><p>Turndown is copyright © 2017 <a href="http://www.domchristie.co.uk/">Dom Christie</a> and is released under the MIT license</p></footer>
<script>
  ;(function () {
    var input = document.getElementById('input')
    var output = document.getElementById('output')
    var optionsForm = document.getElementById('options')
    var turndownService = new window.TurndownService(options())

    input.addEventListener('input', update)

    optionsForm.addEventListener('change', function () {
      turndownService = new window.TurndownService(options())
      update()
    })

    update()

    function update () {
      output.value = turndownService.turndown(input.value)
    }

    function options () {
      var opts = {}
      var inputs = optionsForm.getElementsByTagName('select')
      for (var i = 0; i < inputs.length; i++) {
        var input = inputs[i]
        opts[input.name] = input.value
      }
      return opts
    }
  })()
</script>
</body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "turndown",
  "description": "A library that converts HTML to Markdown",
  "version": "7.2.2",
  "author": "Dom Christie",
  "main": "lib/turndown.cjs.js",
  "module": "lib/turndown.es.js",
  "jsnext:main": "lib/turndown.es.js",
  "browser": {
    "@mixmark-io/domino": false,
    "./lib/turndown.cjs.js": "./lib/turndown.browser.cjs.js",
    "./lib/turndown.es.js": "./lib/turndown.browser.es.js",
    "./lib/turndown.umd.js": "./lib/turndown.browser.umd.js"
  },
  "dependencies": {
    "@mixmark-io/domino": "^2.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.29.0",
    "@babel/plugin-transform-block-scoping": "^7.28.6",
    "@babel/plugin-transform-shorthand-properties": "^7.27.1",
    "@rollup/plugin-babel": "^7.0.0",
    "@rollup/plugin-commonjs": "^29.0.0",
    "@rollup/plugin-node-resolve": "^16.0.3",
    "@rollup/plugin-replace": "^6.0.3",
    "rewire": "^6.0.0",
    "rollup": "^4.59.0",
    "standard": "^17.0.0",
    "turndown-attendant": "0.0.3"
  },
  "engines": {
    "node": ">=18",
    "npm": ">=9"
  },
  "files": [
    "lib",
    "dist"
  ],
  "keywords": [
    "converter",
    "html",
    "markdown"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/mixmark-io/turndown.git"
  },
  "scripts": {
    "build": "npm run build-cjs && npm run build-es && npm run build-umd && npm run build-iife",
    "build-cjs": "rollup -c config/rollup.config.cjs.mjs && rollup -c config/rollup.config.browser.cjs.mjs",
    "build-es": "rollup -c config/rollup.config.es.mjs && rollup -c config/rollup.config.browser.es.mjs",
    "build-umd": "rollup -c config/rollup.config.umd.mjs && rollup -c config/rollup.config.browser.umd.mjs",
    "build-iife": "rollup -c config/rollup.config.iife.mjs",
    "build-test": "rollup -c config/rollup.config.test.mjs",
    "prepare": "npm run build",
    "test": "npm run build && npm run build-test && standard ./src/**/*.js && node test/internals-test.js && node test/turndown-test.js"
  }
}


================================================
FILE: src/collapse-whitespace.js
================================================
/**
 * The collapseWhitespace function is adapted from collapse-whitespace
 * by Luc Thevenard.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Luc Thevenard <lucthevenard@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * collapseWhitespace(options) removes extraneous whitespace from an the given element.
 *
 * @param {Object} options
 */
function collapseWhitespace (options) {
  const element = options.element
  const isBlock = options.isBlock
  const isVoid = options.isVoid
  const isPre = options.isPre || function (node) {
    return node.nodeName === 'PRE'
  }

  if (!element.firstChild || isPre(element)) return

  let prevText = null
  let keepLeadingWs = false

  let prev = null
  let node = next(prev, element, isPre)

  while (node !== element) {
    if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE
      let text = node.data.replace(/[ \r\n\t]+/g, ' ')

      if ((!prevText || / $/.test(prevText.data)) &&
          !keepLeadingWs && text[0] === ' ') {
        text = text.substr(1)
      }

      // `text` might be empty at this point.
      if (!text) {
        node = remove(node)
        continue
      }

      node.data = text

      prevText = node
    } else if (node.nodeType === 1) { // Node.ELEMENT_NODE
      if (isBlock(node) || node.nodeName === 'BR') {
        if (prevText) {
          prevText.data = prevText.data.replace(/ $/, '')
        }

        prevText = null
        keepLeadingWs = false
      } else if (isVoid(node) || isPre(node)) {
        // Avoid trimming space around non-block, non-BR void elements and inline PRE.
        prevText = null
        keepLeadingWs = true
      } else if (prevText) {
        // Drop protection if set previously.
        keepLeadingWs = false
      }
    } else {
      node = remove(node)
      continue
    }

    const nextNode = next(prev, node, isPre)
    prev = node
    node = nextNode
  }

  if (prevText) {
    prevText.data = prevText.data.replace(/ $/, '')
    if (!prevText.data) {
      remove(prevText)
    }
  }
}

/**
 * remove(node) removes the given node from the DOM and returns the
 * next node in the sequence.
 *
 * @param {Node} node
 * @return {Node} node
 */
function remove (node) {
  const next = node.nextSibling || node.parentNode

  node.parentNode.removeChild(node)

  return next
}

/**
 * next(prev, current, isPre) returns the next node in the sequence, given the
 * current and previous nodes.
 *
 * @param {Node} prev
 * @param {Node} current
 * @param {Function} isPre
 * @return {Node}
 */
function next (prev, current, isPre) {
  if ((prev && prev.parentNode === current) || isPre(current)) {
    return current.nextSibling || current.parentNode
  }

  return current.firstChild || current.nextSibling || current.parentNode
}

export default collapseWhitespace


================================================
FILE: src/commonmark-rules.js
================================================
import { escapeMarkdown, repeat, trimNewlines } from './utilities'

const rules = {}

rules.paragraph = {
  filter: 'p',

  replacement: function (content) {
    return '\n\n' + content + '\n\n'
  }
}

rules.lineBreak = {
  filter: 'br',

  replacement: function (content, node, options) {
    return options.br + '\n'
  }
}

rules.heading = {
  filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],

  replacement: function (content, node, options) {
    const hLevel = Number(node.nodeName.charAt(1))

    if (options.headingStyle === 'setext' && hLevel < 3) {
      const underline = repeat((hLevel === 1 ? '=' : '-'), content.length)
      return (
        '\n\n' + content + '\n' + underline + '\n\n'
      )
    } else {
      return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'
    }
  }
}

rules.blockquote = {
  filter: 'blockquote',

  replacement: function (content) {
    content = trimNewlines(content).replace(/^/gm, '> ')
    return '\n\n' + content + '\n\n'
  }
}

rules.list = {
  filter: ['ul', 'ol'],

  replacement: function (content, node) {
    const parent = node.parentNode
    if (parent.nodeName === 'LI' && parent.lastElementChild === node) {
      return '\n' + content
    } else {
      return '\n\n' + content + '\n\n'
    }
  }
}

rules.listItem = {
  filter: 'li',

  replacement: function (content, node, options) {
    let prefix = options.bulletListMarker + '   '
    const parent = node.parentNode
    if (parent.nodeName === 'OL') {
      const start = parent.getAttribute('start')
      const index = Array.prototype.indexOf.call(parent.children, node)
      prefix = (start ? Number(start) + index : index + 1) + '.  '
    }
    const isParagraph = /\n$/.test(content)
    content = trimNewlines(content) + (isParagraph ? '\n' : '')
    content = content.replace(/\n/gm, '\n' + ' '.repeat(prefix.length)) // indent
    return (
      prefix + content + (node.nextSibling ? '\n' : '')
    )
  }
}

rules.indentedCodeBlock = {
  filter: function (node, options) {
    return (
      options.codeBlockStyle === 'indented' &&
      node.nodeName === 'PRE' &&
      node.firstChild &&
      node.firstChild.nodeName === 'CODE'
    )
  },

  replacement: function (content, node, options) {
    return (
      '\n\n    ' +
      node.firstChild.textContent.replace(/\n/g, '\n    ') +
      '\n\n'
    )
  }
}

rules.fencedCodeBlock = {
  filter: function (node, options) {
    return (
      options.codeBlockStyle === 'fenced' &&
      node.nodeName === 'PRE' &&
      node.firstChild &&
      node.firstChild.nodeName === 'CODE'
    )
  },

  replacement: function (content, node, options) {
    const className = node.firstChild.getAttribute('class') || ''
    const language = (className.match(/language-(\S+)/) || [null, ''])[1]
    const code = node.firstChild.textContent

    const fenceChar = options.fence.charAt(0)
    let fenceSize = 3
    const fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm')

    let match
    while ((match = fenceInCodeRegex.exec(code))) {
      if (match[0].length >= fenceSize) {
        fenceSize = match[0].length + 1
      }
    }

    const fence = repeat(fenceChar, fenceSize)

    return (
      '\n\n' + fence + language + '\n' +
      code.replace(/\n$/, '') +
      '\n' + fence + '\n\n'
    )
  }
}

rules.horizontalRule = {
  filter: 'hr',

  replacement: function (content, node, options) {
    return '\n\n' + options.hr + '\n\n'
  }
}

rules.inlineLink = {
  filter: function (node, options) {
    return (
      options.linkStyle === 'inlined' &&
      node.nodeName === 'A' &&
      node.getAttribute('href')
    )
  },

  replacement: function (content, node) {
    const href = escapeLinkDestination(node.getAttribute('href'))
    const title = escapeLinkTitle(cleanAttribute(node.getAttribute('title')))
    const titlePart = title ? ' "' + title + '"' : ''
    return '[' + content + '](' + href + titlePart + ')'
  }
}

rules.referenceLink = {
  filter: function (node, options) {
    return (
      options.linkStyle === 'referenced' &&
      node.nodeName === 'A' &&
      node.getAttribute('href')
    )
  },

  replacement: function (content, node, options) {
    const href = escapeLinkDestination(node.getAttribute('href'))
    let title = cleanAttribute(node.getAttribute('title'))
    if (title) title = ' "' + escapeLinkTitle(title) + '"'
    let replacement
    let reference

    switch (options.linkReferenceStyle) {
      case 'collapsed':
        replacement = '[' + content + '][]'
        reference = '[' + content + ']: ' + href + title
        break
      case 'shortcut':
        replacement = '[' + content + ']'
        reference = '[' + content + ']: ' + href + title
        break
      default:
        var id = this.references.length + 1
        replacement = '[' + content + '][' + id + ']'
        reference = '[' + id + ']: ' + href + title
    }

    this.references.push(reference)
    return replacement
  },

  references: [],

  append: function (options) {
    let references = ''
    if (this.references.length) {
      references = '\n\n' + this.references.join('\n') + '\n\n'
      this.references = [] // Reset references
    }
    return references
  }
}

rules.emphasis = {
  filter: ['em', 'i'],

  replacement: function (content, node, options) {
    if (!content.trim()) return ''
    return options.emDelimiter + content + options.emDelimiter
  }
}

rules.strong = {
  filter: ['strong', 'b'],

  replacement: function (content, node, options) {
    if (!content.trim()) return ''
    return options.strongDelimiter + content + options.strongDelimiter
  }
}

rules.code = {
  filter: function (node) {
    const hasSiblings = node.previousSibling || node.nextSibling
    const isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings

    return node.nodeName === 'CODE' && !isCodeBlock
  },

  replacement: function (content) {
    if (!content) return ''
    content = content.replace(/\r?\n|\r/g, ' ')

    const extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : ''
    let delimiter = '`'
    const matches = content.match(/`+/gm) || []
    while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'

    return delimiter + extraSpace + content + extraSpace + delimiter
  }
}

rules.image = {
  filter: 'img',

  replacement: function (content, node) {
    const alt = escapeMarkdown(cleanAttribute(node.getAttribute('alt')))
    const src = escapeLinkDestination(node.getAttribute('src') || '')
    const title = cleanAttribute(node.getAttribute('title'))
    const titlePart = title ? ' "' + escapeLinkTitle(title) + '"' : ''
    return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
  }
}

function cleanAttribute (attribute) {
  return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
}

function escapeLinkDestination (destination) {
  const escaped = destination.replace(/([<>()])/g, '\\$1')
  return escaped.indexOf(' ') >= 0 ? '<' + escaped + '>' : escaped
}

function escapeLinkTitle (title) {
  return title.replace(/"/g, '\\"')
}

export default rules


================================================
FILE: src/html-parser.js
================================================
/*
 * Set up window for Node.js
 */

const root = (typeof window !== 'undefined' ? window : {})

/*
 * Parsing HTML strings
 */

function canParseHTMLNatively () {
  const Parser = root.DOMParser
  let canParse = false

  // Adapted from https://gist.github.com/1129031
  // Firefox/Opera/IE throw errors on unsupported types
  try {
    // WebKit returns null on unsupported types
    if (new Parser().parseFromString('', 'text/html')) {
      canParse = true
    }
  } catch (e) {}

  return canParse
}

function createHTMLParser () {
  const Parser = function () {}

  if (process.browser) {
    if (shouldUseActiveX()) {
      Parser.prototype.parseFromString = function (string) {
        const doc = new window.ActiveXObject('htmlfile')
        doc.designMode = 'on' // disable on-page scripts
        doc.open()
        doc.write(string)
        doc.close()
        return doc
      }
    } else {
      Parser.prototype.parseFromString = function (string) {
        const doc = document.implementation.createHTMLDocument('')
        doc.open()
        doc.write(string)
        doc.close()
        return doc
      }
    }
  } else {
    const domino = require('@mixmark-io/domino')
    Parser.prototype.parseFromString = function (string) {
      return domino.createDocument(string)
    }
  }
  return Parser
}

function shouldUseActiveX () {
  let useActiveX = false
  try {
    document.implementation.createHTMLDocument('').open()
  } catch (e) {
    if (root.ActiveXObject) useActiveX = true
  }
  return useActiveX
}

export default canParseHTMLNatively() ? root.DOMParser : createHTMLParser()


================================================
FILE: src/node.js
================================================
import { isBlock, isVoid, hasVoid, isMeaningfulWhenBlank, hasMeaningfulWhenBlank } from './utilities'

export default function Node (node, options) {
  node.isBlock = isBlock(node)
  node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode
  node.isBlank = isBlank(node)
  node.flankingWhitespace = flankingWhitespace(node, options)
  return node
}

function isBlank (node) {
  return (
    !isVoid(node) &&
    !isMeaningfulWhenBlank(node) &&
    /^\s*$/i.test(node.textContent) &&
    !hasVoid(node) &&
    !hasMeaningfulWhenBlank(node)
  )
}

function flankingWhitespace (node, options) {
  if (node.isBlock || (options.preformattedCode && node.isCode)) {
    return { leading: '', trailing: '' }
  }

  const edges = edgeWhitespace(node.textContent)

  // abandon leading ASCII WS if left-flanked by ASCII WS
  if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) {
    edges.leading = edges.leadingNonAscii
  }

  // abandon trailing ASCII WS if right-flanked by ASCII WS
  if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) {
    edges.trailing = edges.trailingNonAscii
  }

  return { leading: edges.leading, trailing: edges.trailing }
}

function edgeWhitespace (string) {
  const m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/)
  return {
    leading: m[1], // whole string for whitespace-only strings
    leadingAscii: m[2],
    leadingNonAscii: m[3],
    trailing: m[4], // empty for whitespace-only strings
    trailingNonAscii: m[5],
    trailingAscii: m[6]
  }
}

function isFlankedByWhitespace (side, node, options) {
  let sibling
  let regExp
  let isFlanked

  if (side === 'left') {
    sibling = node.previousSibling
    regExp = / $/
  } else {
    sibling = node.nextSibling
    regExp = /^ /
  }

  if (sibling) {
    if (sibling.nodeType === 3) {
      isFlanked = regExp.test(sibling.nodeValue)
    } else if (options.preformattedCode && sibling.nodeName === 'CODE') {
      isFlanked = false
    } else if (sibling.nodeType === 1 && !isBlock(sibling)) {
      isFlanked = regExp.test(sibling.textContent)
    }
  }
  return isFlanked
}


================================================
FILE: src/root-node.js
================================================
import collapseWhitespace from './collapse-whitespace'
import HTMLParser from './html-parser'
import { isBlock, isVoid } from './utilities'

export default function RootNode (input, options) {
  let root
  if (typeof input === 'string') {
    const doc = htmlParser().parseFromString(
      // DOM parsers arrange elements in the <head> and <body>.
      // Wrapping in a custom element ensures elements are reliably arranged in
      // a single element.
      '<x-turndown id="turndown-root">' + input + '</x-turndown>',
      'text/html'
    )
    root = doc.getElementById('turndown-root')
  } else {
    root = input.cloneNode(true)
  }
  normalizePre(root)
  collapseWhitespace({
    element: root,
    isBlock,
    isVoid,
    isPre: options.preformattedCode ? isPreOrCode : null
  })

  return root
}

let _htmlParser
function htmlParser () {
  _htmlParser = _htmlParser || new HTMLParser()
  return _htmlParser
}

function isPreOrCode (node) {
  return node.nodeName === 'PRE' || node.nodeName === 'CODE'
}

function normalizePre (root) {
  if (!root.getElementsByTagName) {
    return // unsupported DOM method
  }
  const preNodes = root.getElementsByTagName('PRE')
  for (let i = 0; i < preNodes.length; i++) {
    const brNodes = preNodes[i].getElementsByTagName('BR')
    while (brNodes.length > 0) {
      brNodes[0].parentNode.replaceChild(
        brNodes[0].ownerDocument.createTextNode('\n'),
        brNodes[0]
      )
    }
  }
}


================================================
FILE: src/rules.js
================================================
/**
 * Manages a collection of rules used to convert HTML to Markdown
 */

export default function Rules (options) {
  this.options = options
  this._keep = []
  this._remove = []

  this.blankRule = {
    replacement: options.blankReplacement
  }

  this.keepReplacement = options.keepReplacement

  this.defaultRule = {
    replacement: options.defaultReplacement
  }

  this.array = []
  for (const key in options.rules) this.array.push(options.rules[key])
}

Rules.prototype = {
  add: function (key, rule) {
    this.array.unshift(rule)
  },

  keep: function (filter) {
    this._keep.unshift({
      filter,
      replacement: this.keepReplacement
    })
  },

  remove: function (filter) {
    this._remove.unshift({
      filter,
      replacement: function () {
        return ''
      }
    })
  },

  forNode: function (node) {
    if (node.isBlank) return this.blankRule
    let rule

    if ((rule = findRule(this.array, node, this.options))) return rule
    if ((rule = findRule(this._keep, node, this.options))) return rule
    if ((rule = findRule(this._remove, node, this.options))) return rule

    return this.defaultRule
  },

  forEach: function (fn) {
    for (let i = 0; i < this.array.length; i++) fn(this.array[i], i)
  }
}

function findRule (rules, node, options) {
  for (let i = 0; i < rules.length; i++) {
    const rule = rules[i]
    if (filterValue(rule, node, options)) return rule
  }
  return undefined
}

function filterValue (rule, node, options) {
  const filter = rule.filter
  if (typeof filter === 'string') {
    if (filter === node.nodeName.toLowerCase()) return true
  } else if (Array.isArray(filter)) {
    if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true
  } else if (typeof filter === 'function') {
    if (filter.call(rule, node, options)) return true
  } else {
    throw new TypeError('`filter` needs to be a string, array, or function')
  }
}


================================================
FILE: src/turndown.js
================================================
import COMMONMARK_RULES from './commonmark-rules'
import Rules from './rules'
import { escapeMarkdown, extend, trimLeadingNewlines, trimTrailingNewlines } from './utilities'
import RootNode from './root-node'
import Node from './node'
const reduce = Array.prototype.reduce

export default function TurndownService (options) {
  if (!(this instanceof TurndownService)) return new TurndownService(options)

  const defaults = {
    rules: COMMONMARK_RULES,
    headingStyle: 'setext',
    hr: '* * *',
    bulletListMarker: '*',
    codeBlockStyle: 'indented',
    fence: '```',
    emDelimiter: '_',
    strongDelimiter: '**',
    linkStyle: 'inlined',
    linkReferenceStyle: 'full',
    br: '  ',
    preformattedCode: false,
    blankReplacement: function (content, node) {
      return node.isBlock ? '\n\n' : ''
    },
    keepReplacement: function (content, node) {
      return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
    },
    defaultReplacement: function (content, node) {
      return node.isBlock ? '\n\n' + content + '\n\n' : content
    }
  }
  this.options = extend({}, defaults, options)
  this.rules = new Rules(this.options)
}

TurndownService.prototype = {
  /**
   * The entry point for converting a string or DOM node to Markdown
   * @public
   * @param {String|HTMLElement} input The string or DOM node to convert
   * @returns A Markdown representation of the input
   * @type String
   */

  turndown: function (input) {
    if (!canConvert(input)) {
      throw new TypeError(
        input + ' is not a string, or an element/document/fragment node.'
      )
    }

    if (input === '') return ''

    const output = process.call(this, new RootNode(input, this.options))
    return postProcess.call(this, output)
  },

  /**
   * Add one or more plugins
   * @public
   * @param {Function|Array} plugin The plugin or array of plugins to add
   * @returns The Turndown instance for chaining
   * @type Object
   */

  use: function (plugin) {
    if (Array.isArray(plugin)) {
      for (let i = 0; i < plugin.length; i++) this.use(plugin[i])
    } else if (typeof plugin === 'function') {
      plugin(this)
    } else {
      throw new TypeError('plugin must be a Function or an Array of Functions')
    }
    return this
  },

  /**
   * Adds a rule
   * @public
   * @param {String} key The unique key of the rule
   * @param {Object} rule The rule
   * @returns The Turndown instance for chaining
   * @type Object
   */

  addRule: function (key, rule) {
    this.rules.add(key, rule)
    return this
  },

  /**
   * Keep a node (as HTML) that matches the filter
   * @public
   * @param {String|Array|Function} filter The unique key of the rule
   * @returns The Turndown instance for chaining
   * @type Object
   */

  keep: function (filter) {
    this.rules.keep(filter)
    return this
  },

  /**
   * Remove a node that matches the filter
   * @public
   * @param {String|Array|Function} filter The unique key of the rule
   * @returns The Turndown instance for chaining
   * @type Object
   */

  remove: function (filter) {
    this.rules.remove(filter)
    return this
  },

  /**
   * Escapes Markdown syntax
   * @public
   * @param {String} string The string to escape
   * @returns A string with Markdown syntax escaped
   * @type String
   */

  escape: function (string) {
    return escapeMarkdown(string)
  }
}

/**
 * Reduces a DOM node down to its Markdown string equivalent
 * @private
 * @param {HTMLElement} parentNode The node to convert
 * @returns A Markdown representation of the node
 * @type String
 */

function process (parentNode) {
  const self = this
  return reduce.call(parentNode.childNodes, function (output, node) {
    node = new Node(node, self.options)

    let replacement = ''
    if (node.nodeType === 3) {
      replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue)
    } else if (node.nodeType === 1) {
      replacement = replacementForNode.call(self, node)
    }

    return join(output, replacement)
  }, '')
}

/**
 * Appends strings as each rule requires and trims the output
 * @private
 * @param {String} output The conversion output
 * @returns A trimmed version of the ouput
 * @type String
 */

function postProcess (output) {
  const self = this
  this.rules.forEach(function (rule) {
    if (typeof rule.append === 'function') {
      output = join(output, rule.append(self.options))
    }
  })

  return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '')
}

/**
 * Converts an element node to its Markdown equivalent
 * @private
 * @param {HTMLElement} node The node to convert
 * @returns A Markdown representation of the node
 * @type String
 */

function replacementForNode (node) {
  const rule = this.rules.forNode(node)
  let content = process.call(this, node)
  const whitespace = node.flankingWhitespace
  if (whitespace.leading || whitespace.trailing) content = content.trim()
  return (
    whitespace.leading +
    rule.replacement(content, node, this.options) +
    whitespace.trailing
  )
}

/**
 * Joins replacement to the current output with appropriate number of new lines
 * @private
 * @param {String} output The current conversion output
 * @param {String} replacement The string to append to the output
 * @returns Joined output
 * @type String
 */

function join (output, replacement) {
  const s1 = trimTrailingNewlines(output)
  const s2 = trimLeadingNewlines(replacement)
  const nls = Math.max(output.length - s1.length, replacement.length - s2.length)
  const separator = '\n\n'.substring(0, nls)

  return s1 + separator + s2
}

/**
 * Determines whether an input can be converted
 * @private
 * @param {String|HTMLElement} input Describe this parameter
 * @returns Describe what it returns
 * @type String|Object|Array|Boolean|Number
 */

function canConvert (input) {
  return (
    input != null && (
      typeof input === 'string' ||
      (input.nodeType && (
        input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11
      ))
    )
  )
}


================================================
FILE: src/utilities.js
================================================
export function extend (destination) {
  for (let i = 1; i < arguments.length; i++) {
    const source = arguments[i]
    for (const key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) destination[key] = source[key]
    }
  }
  return destination
}

export function repeat (character, count) {
  return Array(count + 1).join(character)
}

export function trimLeadingNewlines (string) {
  return string.replace(/^\n*/, '')
}

export function trimTrailingNewlines (string) {
  // avoid match-at-end regexp bottleneck, see #370
  let indexEnd = string.length
  while (indexEnd > 0 && string[indexEnd - 1] === '\n') indexEnd--
  return string.substring(0, indexEnd)
}

export function trimNewlines (string) {
  return trimTrailingNewlines(trimLeadingNewlines(string))
}

export var blockElements = [
  'ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS',
  'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE',
  'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER',
  'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES',
  'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD',
  'TFOOT', 'TH', 'THEAD', 'TR', 'UL'
]

export function isBlock (node) {
  return is(node, blockElements)
}

export var voidElements = [
  'AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT',
  'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR'
]

export function isVoid (node) {
  return is(node, voidElements)
}

export function hasVoid (node) {
  return has(node, voidElements)
}

const meaningfulWhenBlankElements = [
  'A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT',
  'AUDIO', 'VIDEO'
]

export function isMeaningfulWhenBlank (node) {
  return is(node, meaningfulWhenBlankElements)
}

export function hasMeaningfulWhenBlank (node) {
  return has(node, meaningfulWhenBlankElements)
}

function is (node, tagNames) {
  return tagNames.indexOf(node.nodeName) >= 0
}

function has (node, tagNames) {
  return (
    node.getElementsByTagName &&
    tagNames.some(function (tagName) {
      return node.getElementsByTagName(tagName).length
    })
  )
}

const markdownEscapes = [
  [/\\/g, '\\\\'],
  [/\*/g, '\\*'],
  [/^-/g, '\\-'],
  [/^\+ /g, '\\+ '],
  [/^(=+)/g, '\\$1'],
  [/^(#{1,6}) /g, '\\$1 '],
  [/`/g, '\\`'],
  [/^~~~/g, '\\~~~'],
  [/\[/g, '\\['],
  [/\]/g, '\\]'],
  [/^>/g, '\\>'],
  [/_/g, '\\_'],
  [/^(\d+)\. /g, '$1\\. ']
]

export function escapeMarkdown (string) {
  return markdownEscapes.reduce(function (accumulator, escape) {
    return accumulator.replace(escape[0], escape[1])
  }, string)
}


================================================
FILE: test/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>turndown test runner</title>
  <link rel="stylesheet" href="../node_modules/turndown-attendant/dist/styles.css">
</head>
<body>

<!-- TEST CASES -->

<div class="case" data-name="p">
  <div class="input"><p>Lorem ipsum</p></div>
  <pre class="expected">Lorem ipsum</pre>
</div>

<div class="case" data-name="multiple ps">
  <div class="input">
    <p>Lorem</p>
    <p>ipsum</p>
    <p>sit</p>
  </div>
  <pre class="expected">Lorem

ipsum

sit</pre>
</div>

<div class="case" data-name="em">
  <div class="input"><em>em element</em></div>
  <pre class="expected">_em element_</pre>
</div>

<div class="case" data-name="i">
  <div class="input"><i>i element</i></div>
  <pre class="expected">_i element_</pre>
</div>

<div class="case" data-name="strong">
  <div class="input"><strong>strong element</strong></div>
  <pre class="expected">**strong element**</pre>
</div>

<div class="case" data-name="b">
  <div class="input"><b>b element</b></div>
  <pre class="expected">**b element**</pre>
</div>

<div class="case" data-name="code">
  <div class="input"><code>code element</code></div>
  <pre class="expected">`code element`</pre>
</div>

<div class="case" data-name="code containing a backtick">
  <div class="input"><code>There is a literal backtick (`) here</code></div>
  <pre class="expected">``There is a literal backtick (`) here``</pre>
</div>

<div class="case" data-name="code containing three or more backticks">
  <div class="input"><code>here are three ``` here are four ```` that's it</code></div>
  <pre class="expected">`here are three ``` here are four ```` that's it`</pre>
</div>

<div class="case" data-name="code containing one or more backticks">
  <div class="input"><code>here are three ``` here are four ```` here is one ` that's it</code></div>
  <pre class="expected">``here are three ``` here are four ```` here is one ` that's it``</pre>
</div>

<div class="case" data-name="code starting with a backtick">
  <div class="input"><code>`starting with a backtick</code></div>
  <pre class="expected">`` `starting with a backtick ``</pre>
</div>

<div class="case" data-name="code containing markdown syntax">
  <div class="input"><code>_emphasis_</code></div>
  <pre class="expected">`_emphasis_`</pre>
</div>

<div class="case" data-name="code containing markdown syntax in a span">
  <div class="input"><code><span>_emphasis_</span></code></div>
  <pre class="expected">`_emphasis_`</pre>
</div>

<div class="case" data-name="h1">
  <div class="input"><h1>Level One Heading</h1></div>
  <pre class="expected">Level One Heading
=================</pre>
</div>

<div class="case" data-name="escape = when used as heading">
  <div class="input">===</div>
  <pre class="expected">\===</pre>
</div>

<div class="case" data-name="not escaping = outside of a heading">
  <div class="input">A sentence containing =</div>
  <pre class="expected">A sentence containing =</pre>
</div>

<div class="case" data-name="h1 as atx" data-options='{"headingStyle":"atx"}'>
  <div class="input"><h1>Level One Heading with ATX</h1></div>
  <pre class="expected"># Level One Heading with ATX</pre>
</div>

<div class="case" data-name="h2">
  <div class="input"><h2>Level Two Heading</h2></div>
  <pre class="expected">Level Two Heading
-----------------</pre>
</div>

<div class="case" data-name="h2 as atx" data-options='{"headingStyle":"atx"}'>
  <div class="input"><h2>Level Two Heading with ATX</h2></div>
  <pre class="expected">## Level Two Heading with ATX</pre>
</div>

<div class="case" data-name="h3">
  <div class="input"><h3>Level Three Heading</h3></div>
  <pre class="expected">### Level Three Heading</pre>
</div>

<div class="case" data-name="heading with child">
  <div class="input"><h4>Level Four Heading with <code>child</code></h4></div>
  <pre class="expected">#### Level Four Heading with `child`</pre>
</div>

<div class="case" data-name="invalid heading">
  <div class="input"><h7>Level Seven Heading?</h7></div>
  <pre class="expected">Level Seven Heading?</pre>
</div>

<div class="case" data-name="hr">
  <div class="input"><hr></div>
  <pre class="expected">* * *</pre>
</div>

<div class="case" data-name="hr with closing tag">
  <div class="input"><hr></hr></div>
  <pre class="expected">* * *</pre>
</div>

<div class="case" data-name="hr with option" data-options='{"hr": "- - -"}'>
  <div class="input"><hr></div>
  <pre class="expected">- - -</pre>
</div>

<div class="case" data-name="br">
  <div class="input">More<br>after the break</div>
  <pre class="expected">More  
after the break</pre>
</div>

<div class="case" data-name="br with visible line-ending" data-options='{"br": "\\"}'>
  <div class="input">More<br>after the break</div>
  <pre class="expected">More\
after the break</pre>
</div>

<div class="case" data-name="img with no alt">
  <div class="input"><img src="http://example.com/logo.png" /></div>
  <pre class="expected">![](http://example.com/logo.png)</pre>
</div>

<div class="case" data-name="img with relative src">
  <div class="input"><img src="logo.png"></div>
  <pre class="expected">![](logo.png)</pre>
</div>

<div class="case" data-name="img with alt">
  <div class="input"><img src="logo.png" alt="img with alt"></div>
  <pre class="expected">![img with alt](logo.png)</pre>
</div>

<div class="case" data-name="img with escaped content in alt">
  <div class="input"><img src="logo.png" alt="_img_ *with* [alt]"></div>
  <pre class="expected">![\_img\_ \*with\* \[alt\]](logo.png)</pre>
</div>

<div class="case" data-name="img with no src">
  <div class="input"><img></div>
  <pre class="expected"></pre>
</div>

<div class="case" data-name="img with parenthesis in src">
  <div class="input"><img src="logo.png?(query)"></div>
  <pre class="expected">![](logo.png?\(query\))</pre>
</div>

<div class="case" data-name="img with a new line in alt">
  <div class="input"><img src="logo.png" alt="img with
    alt"></div>
  <pre class="expected">![img with
alt](logo.png)</pre>
</div>

<div class="case" data-name="img with more than one new line in alt">
  <div class="input"><img src="logo.png" alt="img with
    
    alt"></div>
  <pre class="expected">![img with
alt](logo.png)</pre>
</div>

<div class="case" data-name="img with new lines in title">
  <div class="input"><img src="logo.png" title="the
    
    title"></div>
  <pre class="expected">![](logo.png "the
title")</pre>
</div>

<div class="case" data-name="img with quotes in title">
  <div class="input"><img src="logo.png" title="&quot;hello&quot;"></div>
  <pre class="expected">![](logo.png "\"hello\"")</pre>
</div>

<div class="case" data-name="img with space in URL">
  <div class="input"><img src="logo 1.png" alt="An image"></div>
  <pre class="expected">![An image](&lt;logo 1.png&gt;)</pre>
</div>

<div class="case" data-name="a">
  <div class="input"><a href="http://example.com">An anchor</a></div>
  <pre class="expected">[An anchor](http://example.com)</pre>
</div>

<div class="case" data-name="a with space in URL">
  <div class="input"><a href="http://example.com/foo bar">An anchor</a></div>
  <pre class="expected">[An anchor](&lt;http://example.com/foo bar&gt;)</pre>
</div>

<div class="case" data-name="a with title">
  <div class="input"><a href="http://example.com" title="Title for link">An anchor</a></div>
  <pre class="expected">[An anchor](http://example.com "Title for link")</pre>
</div>

<div class="case" data-name="a with multiline title">
  <div class="input"><a href="http://example.com" title="Title for
    
    link">An anchor</a></div>
  <pre class="expected">[An anchor](http://example.com "Title for
link")</pre>
</div>

<div class="case" data-name="a with quotes in title">
  <div class="input"><a href="http://example.com" title="&quot;hello&quot;">An anchor</a></div>
  <pre class="expected">[An anchor](http://example.com "\"hello\"")</pre>
</div>

<div class="case" data-name="a with parenthesis in query">
  <div class="input"><a href="http://example.com?(query)">An anchor</a></div>
  <pre class="expected">[An anchor](http://example.com?\(query\))</pre>
</div>

<div class="case" data-name="a without a src">
  <div class="input"><a id="about-anchor">Anchor without a title</a></div>
  <pre class="expected">Anchor without a title</pre>
</div>

<div class="case" data-name="a with a child">
  <div class="input"><a href="http://example.com/code">Some <code>code</code></a></div>
  <pre class="expected">[Some `code`](http://example.com/code)</pre>
</div>

<div class="case" data-name="a with a brackets in text">
  <div class="input"><a href="http://example.com/code">Some [text]</a></div>
  <pre class="expected">[Some \[text\]](http://example.com/code)</pre>
</div>

<div class="case" data-name="a reference" data-options='{"linkStyle": "referenced"}'>
  <div class="input"><a href="http://example.com">Reference link</a></div>
  <pre class="expected">[Reference link][1]

[1]: http://example.com</pre>
</div>

<div class="case" data-name="a reference with space in URL" data-options='{"linkStyle": "referenced"}'>
  <div class="input"><a href="http://example.com/foo bar">Reference link</a></div>
  <pre class="expected">[Reference link][1]

[1]: &lt;http://example.com/foo bar&gt;</pre>
</div>

<div class="case" data-name="a reference with collapsed style" data-options='{"linkStyle": "referenced", "linkReferenceStyle": "collapsed"}'>
  <div class="input"><a href="http://example.com">Reference link with collapsed style</a></div>
  <pre class="expected">[Reference link with collapsed style][]

[Reference link with collapsed style]: http://example.com</pre>
</div>

<div class="case" data-name="a reference with shortcut style" data-options='{"linkStyle": "referenced", "linkReferenceStyle": "shortcut"}'>
  <div class="input"><a href="http://example.com">Reference link with shortcut style</a></div>
  <pre class="expected">[Reference link with shortcut style]

[Reference link with shortcut style]: http://example.com</pre>
</div>

<div class="case" data-name="a reference with title" data-options='{"linkStyle": "referenced"}'>
  <div class="input"><a href="http://example.com" title="Hello &quot; World">Reference link</a></div>
  <pre class="expected">[Reference link][1]

[1]: http://example.com "Hello \" World"</pre>
</div>

<div class="case" data-name="pre/code block">
  <div class="input"><pre><code>def code_block
  # 42 &lt; 9001
  "Hello world!"
end</code></pre></div>

  <pre class="expected">    def code_block
      # 42 < 9001
      "Hello world!"
    end</pre>
</div>

<div class="case" data-name="multiple pre/code blocks">
  <div class="input"><pre><code>def first_code_block
  # 42 &lt; 9001
  "Hello world!"
end</code></pre>

<p>next:</p>

<pre><code>def second_code_block
  # 42 &lt; 9001
  "Hello world!"
end</code></pre></div>

  <pre class="expected">    def first_code_block
      # 42 &lt; 9001
      "Hello world!"
    end

next:

    def second_code_block
      # 42 &lt; 9001
      "Hello world!"
    end</pre>
</div>

<div class="case" data-name="pre/code block with multiple new lines">
  <div class="input"><div><pre><code>Multiple new lines


should not be


removed</code></pre></div></div>

  <pre class="expected">    Multiple new lines
    
    
    should not be
    
    
    removed</pre>
</div>

<div class="case" data-name="pre/code block with newline as &lt;br&gt; tag">
  <div class="input"><div><pre><code>First line<br>Second line<br>Third line</code></pre></div></div>

  <pre class="expected">    First line
    Second line
    Third line</pre>
</div>

<div class="case" data-name="fenced pre/code block with newline as &lt;br&gt; tag" data-options='{"codeBlockStyle": "fenced"}'>
  <div class="input"><div><pre><code>First line<br>Second line<br>Third line</code></pre></div></div>

  <pre class="expected">```
First line
Second line
Third line
```</pre>
</div>

<div class="case" data-name="fenced pre/code block" data-options='{"codeBlockStyle": "fenced"}'>
  <div class="input">
    <pre><code>def a_fenced_code block; end</code></pre>
  </div>
  <pre class="expected">```
def a_fenced_code block; end
```</pre>
</div>

<div class="case" data-name="pre/code block fenced with ~" data-options='{"codeBlockStyle": "fenced", "fence": "~~~"}'>
  <div class="input">
    <pre><code>def a_fenced_code block; end</code></pre>
  </div>
  <pre class="expected">~~~
def a_fenced_code block; end
~~~</pre>
</div>

<div class="case" data-name="escaping ~~~">
  <div class="input">
    <pre>~~~ foo</pre>
  </div>
  <pre class="expected">\~~~ foo</pre>
</div>

<div class="case" data-name="not escaping ~~~">
  <div class="input">A sentence containing ~~~</div>
  <pre class="expected">A sentence containing ~~~</pre>
</div>

<div class="case" data-name="fenced pre/code block with language" data-options='{"codeBlockStyle": "fenced"}'>
  <div class="input">
    <pre><code class="language-ruby">def a_fenced_code block; end</code></pre>
  </div>
  <pre class="expected">```ruby
def a_fenced_code block; end
```</pre>
</div>

<div class="case" data-name="empty pre does not throw error">
  <div class="input">
    <pre></pre>
  </div>
  <pre class="expected"></pre>
</div>

<div class="case" data-name="ol">
  <div class="input">
    <ol>
      <li>Ordered list item 1</li>
      <li>Ordered list item 2</li>
      <li>Ordered list item 3</li>
    </ol>
  </div>
  <pre class="expected">1.  Ordered list item 1
2.  Ordered list item 2
3.  Ordered list item 3</pre>
</div>

<div class="case" data-name="ol with start">
  <div class="input">
    <ol start="42">
      <li>Ordered list item 42</li>
      <li>Ordered list item 43</li>
      <li>Ordered list item 44</li>
    </ol>
  </div>
  <pre class="expected">42.  Ordered list item 42
43.  Ordered list item 43
44.  Ordered list item 44</pre>
</div>

<div class="case" data-name="ol with content">
  <div class="input">
    <ol start="42">
      <li>
        <p>Ordered list item 42</p>
        <p>Ordered list's additional content</p>
      </li>
    </ol>
  </div>
  <pre class="expected">42.  Ordered list item 42
     
     Ordered list's additional content</pre>
</div>

<div class="case" data-name="list spacing">
  <div class="input">
    <p>A paragraph.</p>
    <ol>
      <li>Ordered list item 1</li>
      <li>Ordered list item 2</li>
      <li>Ordered list item 3</li>
    </ol>
    <p>Another paragraph.</p>
    <ul>
      <li>Unordered list item 1</li>
      <li>Unordered list item 2</li>
      <li>Unordered list item 3</li>
    </ul>
  </div>
  <pre class="expected">A paragraph.

1.  Ordered list item 1
2.  Ordered list item 2
3.  Ordered list item 3

Another paragraph.

*   Unordered list item 1
*   Unordered list item 2
*   Unordered list item 3</pre>
</div>

<div class="case" data-name="ul">
  <div class="input">
    <ul>
      <li>Unordered list item 1</li>
      <li>Unordered list item 2</li>
      <li>Unordered list item 3</li>
    </ul>
  </div>
  <pre class="expected">*   Unordered list item 1
*   Unordered list item 2
*   Unordered list item 3</pre>
</div>

<div class="case" data-name="ul with custom bullet" data-options='{"bulletListMarker": "-"}'>
  <div class="input">
    <ul>
      <li>Unordered list item 1</li>
      <li>Unordered list item 2</li>
      <li>Unordered list item 3</li>
    </ul>
  </div>
  <pre class="expected">-   Unordered list item 1
-   Unordered list item 2
-   Unordered list item 3</pre>
</div>

<div class="case" data-name="ul with paragraph">
  <div class="input">
    <ul>
      <li><p>List item with paragraph</p></li>
      <li>List item without paragraph</li>
    </ul>
  </div>
  <pre class="expected">*   List item with paragraph
    
*   List item without paragraph</pre>
</div>

<div class="case" data-name="ol with paragraphs">
  <div class="input">
    <ol>
      <li>
        <p>This is a paragraph in a list item.</p>
        <p>This is a paragraph in the same list item as above.</p>
      </li>
      <li>
        <p>A paragraph in a second list item.</p>
      </li>
    </ol>
  </div>
  <pre class="expected">1.  This is a paragraph in a list item.
    
    This is a paragraph in the same list item as above.
    
2.  A paragraph in a second list item.</pre>
</div>

<div class="case" data-name="nested uls">
  <div class="input">
    <ul>
      <li>This is a list item at root level</li>
      <li>This is another item at root level</li>
      <li>
        <ul>
          <li>This is a nested list item</li>
          <li>This is another nested list item</li>
          <li>
            <ul>
              <li>This is a deeply nested list item</li>
              <li>This is another deeply nested list item</li>
              <li>This is a third deeply nested list item</li>
            </ul>
          </li>
        </ul>
      </li>
      <li>This is a third item at root level</li>
    </ul>
  </div>
  <pre class="expected">*   This is a list item at root level
*   This is another item at root level
*   *   This is a nested list item
    *   This is another nested list item
    *   *   This is a deeply nested list item
        *   This is another deeply nested list item
        *   This is a third deeply nested list item
*   This is a third item at root level</pre>
</div>

<div class="case" data-name="nested ols and uls">
  <div class="input">
    <ul>
      <li>This is a list item at root level</li>
      <li>This is another item at root level</li>
      <li>
        <ol>
          <li>This is a nested list item</li>
          <li>This is another nested list item</li>
          <li>
            <ul>
              <li>This is a deeply nested list item</li>
              <li>This is another deeply nested list item</li>
              <li>This is a third deeply nested list item</li>
            </ul>
          </li>
        </ol>
      </li>
      <li>This is a third item at root level</li>
    </ul>
  </div>
  <pre class="expected">*   This is a list item at root level
*   This is another item at root level
*   1.  This is a nested list item
    2.  This is another nested list item
    3.  *   This is a deeply nested list item
        *   This is another deeply nested list item
        *   This is a third deeply nested list item
*   This is a third item at root level</pre>
</div>

<div class="case" data-name="ul with blockquote">
  <div class="input">
    <ul>
      <li>
        <p>A list item with a blockquote:</p>
        <blockquote>
          <p>This is a blockquote inside a list item.</p>
        </blockquote>
      </li>
    </ul>
  </div>
  <pre class="expected">*   A list item with a blockquote:
    
    > This is a blockquote inside a list item.</pre>
</div>

<div class="case" data-name="blockquote">
  <div class="input">
    <blockquote>
      <p>This is a paragraph within a blockquote.</p>
      <p>This is another paragraph within a blockquote.</p>
    </blockquote>
  </div>
  <pre class="expected">> This is a paragraph within a blockquote.
> 
> This is another paragraph within a blockquote.</pre>
</div>

<div class="case" data-name="nested blockquotes">
  <div class="input">
    <blockquote>
      <p>This is the first level of quoting.</p>
      <blockquote>
        <p>This is a paragraph in a nested blockquote.</p>
      </blockquote>
      <p>Back to the first level.</p>
    </blockquote>
  </div>
  <pre class="expected">> This is the first level of quoting.
> 
> > This is a paragraph in a nested blockquote.
> 
> Back to the first level.</pre>
</div>

<div class="case" data-name="html in blockquote">
  <div class="input">
    <blockquote>
      <h2>This is a header.</h2>
      <ol>
        <li>This is the first list item.</li>
        <li>This is the second list item.</li>
      </ol>
      <p>A code block:</p>
      <pre><code>return 1 &lt; 2 ? shell_exec('echo $input | $markdown_script') : 0;</code></pre>
    </blockquote>
  </div>
  <pre class="expected">> This is a header.
> -----------------
> 
> 1.  This is the first list item.
> 2.  This is the second list item.
> 
> A code block:
> 
>     return 1 &lt; 2 ? shell_exec('echo $input | $markdown_script') : 0;</pre>
</div>

<div class="case" data-name="multiple divs">
  <div class="input">
    <div>A div</div>
    <div>Another div</div>
  </div>
  <pre class="expected">A div

Another div</pre>
</div>

<div class="case" data-name="multiple divs">
  <div class="input">
    <div>A div</div>
    <div>Another div</div>
  </div>
  <pre class="expected">A div

Another div</pre>
</div>

<div class="case" data-name="comment">
  <div class="input"><!-- comment --></div>
  <pre class="expected"></pre>
</div>

<div class="case" data-name="pre/code with comment">
  <div class="input">
    <pre ><code>Hello<!-- comment --> world</code></pre>
  </div>
  <pre class="expected">    Hello world</pre>
</div>

<div class="case" data-name="leading whitespace in heading">
  <div class="input"><h3>
    h3 with leading whitespace</h3></div>
  <pre class="expected">### h3 with leading whitespace</pre>
</div>

<div class="case" data-name="trailing whitespace in li">
  <div class="input">
    <ol>
      <li>Chapter One
        <ol>
          <li>Section One</li>
          <li>Section Two with trailing whitespace </li>
          <li>Section Three with trailing whitespace </li>
        </ol>
      </li>
      <li>Chapter Two</li>
      <li>Chapter Three with trailing whitespace  </li>
    </ol>
  </div>
  <pre class="expected">1.  Chapter One
    1.  Section One
    2.  Section Two with trailing whitespace
    3.  Section Three with trailing whitespace
2.  Chapter Two
3.  Chapter Three with trailing whitespace</pre>
</div>

<div class="case" data-name="multilined and bizarre formatting">
  <div class="input">
    <ul>
      <li>
        Indented li with leading/trailing newlines
      </li>
      <li>
        <strong>Strong with trailing space inside li with leading/trailing whitespace </strong> </li>
      <li>li without whitespace</li>
      <li> Leading space, text, lots of whitespace …
                          text
      </li>
    </ol>
  </div>
  <pre class="expected">*   Indented li with leading/trailing newlines
*   **Strong with trailing space inside li with leading/trailing whitespace**
*   li without whitespace
*   Leading space, text, lots of whitespace … text</pre>
</div>

<div class="case" data-name="whitespace between inline elements">
  <div class="input">
    <p>I <a href="http://example.com/need">need</a> <a href="http://www.example.com/more">more</a> spaces!</p>
  </div>
  <pre class="expected">I [need](http://example.com/need) [more](http://www.example.com/more) spaces!</pre>
</div>

<div class="case" data-name="whitespace in inline elements">
  <div class="input">Text with no space after the period.<em> Text in em with leading/trailing spaces </em><strong>text in strong with trailing space </strong></div>
  <pre class="expected">Text with no space after the period. _Text in em with leading/trailing spaces_ **text in strong with trailing space**</pre>
</div>

<div class="case" data-name="whitespace in nested inline elements">
  <div class="input">Text at root <strong><a href="http://www.example.com">link text with trailing space in strong </a></strong>more text at root</div>
  <pre class="expected">Text at root **[link text with trailing space in strong](http://www.example.com)** more text at root</pre>
</div>

<div class="case" data-name="blank inline elements">
  <div class="input">
    Text before blank em … <em></em> text after blank em
  </div>
  <pre class="expected">Text before blank em … text after blank em</pre>
</div>

<div class="case" data-name="blank block elements">
  <div class="input">
    Text before blank div … <div></div> text after blank div
  </div>
  <pre class="expected">Text before blank div …

text after blank div</pre>
</div>

<div class="case" data-name="blank inline element with br">
  <div class="input"><strong><br></strong></div>
  <pre class="expected"></pre>
</div>

<div class="case" data-name="whitespace between blocks">
  <div class="input"><div><div>Content in a nested div</div></div>
<div>Content in another div</div></div>
  <pre class="expected">Content in a nested div

Content in another div</pre>
</div>

<div class="case" data-name="escaping backslashes">
  <div class="input">backslash \</div>
  <pre class="expected">backslash \\</pre>
</div>

<div class="case" data-name="escaping headings with #">
  <div class="input">### This is not a heading</div>
  <pre class="expected">\### This is not a heading</pre>
</div>

<div class="case" data-name="not escaping # outside of a heading">
  <div class="input">#This is not # a heading</div>
  <pre class="expected">#This is not # a heading</pre>
</div>

<div class="case" data-name="escaping em markdown with *">
  <div class="input">To add emphasis, surround text with *. For example: *this is emphasis*</div>
  <pre class="expected">To add emphasis, surround text with \*. For example: \*this is emphasis\*</pre>
</div>

<div class="case" data-name="escaping em markdown with _">
  <div class="input">To add emphasis, surround text with _. For example: _this is emphasis_</div>
  <pre class="expected">To add emphasis, surround text with \_. For example: \_this is emphasis\_</pre>
</div>

<div class="case" data-name="not escaping within code">
  <div class="input"><pre><code>def this_is_a_method; end;</code></pre></div>
  <pre class="expected">    def this_is_a_method; end;</pre>
</div>

<div class="case" data-name="escaping strong markdown with *">
  <div class="input">To add strong emphasis, surround text with **. For example: **this is strong**</div>
  <pre class="expected">To add strong emphasis, surround text with \*\*. For example: \*\*this is strong\*\*</pre>
</div>

<div class="case" data-name="escaping strong markdown with _">
  <div class="input">To add strong emphasis, surround text with __. For example: __this is strong__</div>
  <pre class="expected">To add strong emphasis, surround text with \_\_. For example: \_\_this is strong\_\_</pre>
</div>

<div class="case" data-name="escaping hr markdown with *">
  <div class="input">* * *</div>
  <pre class="expected">\* \* \*</pre>
</div>

<div class="case" data-name="escaping hr markdown with -">
  <div class="input">- - -</div>
  <pre class="expected">\- - -</pre>
</div>

<div class="case" data-name="escaping hr markdown with _">
  <div class="input">_ _ _</div>
  <pre class="expected">\_ \_ \_</pre>
</div>

<div class="case" data-name="escaping hr markdown without spaces">
  <div class="input">***</div>
  <pre class="expected">\*\*\*</pre>
</div>

<div class="case" data-name="escaping hr markdown with more than 3 characters">
  <div class="input">* * * * *</div>
  <pre class="expected">\* \* \* \* \*</pre>
</div>

<div class="case" data-name="escaping ol markdown">
  <div class="input">1984. by George Orwell</div>
  <pre class="expected">1984\. by George Orwell</pre>
</div>

<div class="case" data-name="not escaping . outside of an ol">
  <div class="input">1984.George Orwell wrote 1984.</div>
  <pre class="expected">1984.George Orwell wrote 1984.</pre>
</div>

<div class="case" data-name="escaping ul markdown *">
  <div class="input">* An unordered list item</div>
  <pre class="expected">\* An unordered list item</pre>
</div>

<div class="case" data-name="escaping ul markdown -">
  <div class="input">- An unordered list item</div>
  <pre class="expected">\- An unordered list item</pre>
</div>

<div class="case" data-name="escaping ul markdown +">
  <div class="input">+ An unordered list item</div>
  <pre class="expected">\+ An unordered list item</pre>
</div>

<div class="case" data-name="not escaping - outside of a ul">
  <div class="input">Hello-world, 45 - 3 is 42</div>
  <pre class="expected">Hello-world, 45 - 3 is 42</pre>
</div>

<div class="case" data-name="not escaping + outside of a ul">
  <div class="input">+1 and another +</div>
  <pre class="expected">+1 and another +</pre>
</div>

<div class="case" data-name="escaping *">
  <div class="input">You can use * for multiplication</div>
  <pre class="expected">You can use \* for multiplication</pre>
</div>

<div class="case" data-name="escaping ** inside strong tags">
  <div class="input"><strong>**test</strong></div>
  <pre class="expected">**\*\*test**</pre>
</div>

<div class="case" data-name="escaping _ inside em tags">
  <div class="input"><em>test_italics</em></div>
  <pre class="expected">_test\_italics_</pre>
</div>

<div class="case" data-name="escaping > as blockquote">
  <div class="input">> Blockquote in markdown</div>
  <pre class="expected">\> Blockquote in markdown</pre>
</div>

<div class="case" data-name="escaping > as blockquote without space">
  <div class="input">>Blockquote in markdown</div>
  <pre class="expected">\>Blockquote in markdown</pre>
</div>

<div class="case" data-name="not escaping > outside of a blockquote">
  <div class="input">42 > 1</div>
  <pre class="expected">42 > 1</pre>
</div>

<div class="case" data-name="escaping code">
  <div class="input">`not code`</div>
  <pre class="expected">\`not code\`</pre>
</div>

<div class="case" data-name="escaping []">
  <div class="input">[This] is a sentence with brackets</div>
  <pre class="expected">\[This\] is a sentence with brackets</pre>
</div>

<div class="case" data-name="escaping [">
  <div class="input"><a href="http://www.example.com">c[iao</a></div>
  <pre class="expected">[c\[iao](http://www.example.com)</pre>
</div>

<!-- https://github.com/domchristie/to-markdown/issues/188#issuecomment-332216019 -->
<div class="case" data-name="escaping * performance">
  <div class="input">fasdf *883 asdf wer qweasd fsd asdf asdfaqwe rqwefrsdf</div>
  <pre class="expected">fasdf \*883 asdf wer qweasd fsd asdf asdfaqwe rqwefrsdf</pre>
</div>

<div class="case" data-name="escaping multiple asterisks">
  <div class="input"><p>* * ** It aims to be*</p></div>
  <pre class="expected">\* \* \*\* It aims to be\*</pre>
</div>

<div class="case" data-name="escaping delimiters around short words and numbers">
  <div class="input"><p>_Really_? Is that what it _is_? A **2000** year-old computer?</p></div>
  <pre class="expected">\_Really\_? Is that what it \_is\_? A \*\*2000\*\* year-old computer?</pre>
</div>

<div class="case" data-name="non-markdown block elements">
  <div class="input">
    Foo
    <div>Bar</div>
    Baz
  </div>
  <pre class="expected">Foo

Bar

Baz</pre>
</div>

<div class="case" data-name="non-markdown inline elements">
  <div class="input">
    Foo <span>Bar</span>
  </div>
  <pre class="expected">Foo Bar</pre>
</div>

<div class="case" data-name="blank inline elements">
  <div class="input">
    Hello <em></em>world
  </div>
  <pre class="expected">Hello world</pre>
</div>

<div class="case" data-name="elements with a single void element">
  <div class="input">
    <p><img src="http://example.com/logo.png" /></p>
  </div>
  <pre class="expected">![](http://example.com/logo.png)</pre>
</div>

<div class="case" data-name="elements with a nested void element">
  <div class="input">
    <p><span><img src="http://example.com/logo.png" /></span></p>
  </div>
  <pre class="expected">![](http://example.com/logo.png)</pre>
</div>

<div class="case" data-name="text separated by a space in an element">
  <div class="input">
    <p>Foo<span> </span>Bar</p>
  </div>
  <pre class="expected">Foo Bar</pre>
</div>

<div class="case" data-name="text separated by a non-breaking space in an element">
  <div class="input">
    <p>Foo<span>&nbsp;</span>Bar</p>
  </div>
  <pre class="expected">Foo&nbsp;Bar</pre>
</div>

<div class="case" data-name="triple tildes inside code" data-options='{"codeBlockStyle": "fenced", "fence": "~~~"}'>
  <div class="input">
<pre><code>~~~
Code
~~~
</code></pre>
  </div>
  <pre class="expected">~~~~
~~~
Code
~~~
~~~~</pre>
</div>

<div class="case" data-name="triple ticks inside code" data-options='{"codeBlockStyle": "fenced", "fence": "```"}'>
  <div class="input">
<pre><code>```
Code
```
</code></pre>
  </div>
  <pre class="expected">````
```
Code
```
````</pre>
</div>

<div class="case" data-name="four ticks inside code" data-options='{"codeBlockStyle": "fenced", "fence": "```"}'>
  <div class="input">
<pre><code>````
Code
````
</code></pre>
  </div>
  <pre class="expected">`````
````
Code
````
`````</pre>
</div>

<div class="case" data-name="empty line in start/end of code block" data-options='{"codeBlockStyle": "fenced", "fence": "```"}'>
  <div class="input">
<pre><code>
Code

</code></pre>
  </div>
  <pre class="expected">```

Code

```</pre>
</div>

<div class="case" data-name="text separated by ASCII and nonASCII space in an element">
  <div class="input">
    <p>Foo<span>  &nbsp;  </span>Bar</p>
  </div>
  <pre class="expected">Foo &nbsp; Bar</pre>
</div>

<div class="case" data-name="list-like text with non-breaking spaces">
  <div class="input">&nbsp;1. First<br>&nbsp;2. Second</div>
  <pre class="expected">&nbsp;1. First  <!-- hard break -->
&nbsp;2. Second</pre>
</div>

<div class="case" data-name="element with trailing nonASCII WS followed by nonWS">
  <div class="input"><i>foo&nbsp;</i>bar</div>
  <pre class="expected">_foo_&nbsp;bar</pre>
</div>

<div class="case" data-name="element with trailing nonASCII WS followed by nonASCII WS">
  <div class="input"><i>foo&nbsp;</i>&nbsp;bar</div>
  <pre class="expected">_foo_&nbsp;&nbsp;bar</pre>
</div>

<div class="case" data-name="element with trailing ASCII WS followed by nonASCII WS">
  <div class="input"><i>foo </i>&nbsp;bar</div>
  <pre class="expected">_foo_ &nbsp;bar</pre>
</div>

<div class="case" data-name="element with trailing nonASCII WS followed by ASCII WS">
  <div class="input"><i>foo&nbsp;</i> bar</div>
  <pre class="expected">_foo_&nbsp; bar</pre>
</div>

<div class="case" data-name="nonWS followed by element with leading nonASCII WS">
  <div class="input">foo<i>&nbsp;bar</i></div>
  <pre class="expected">foo&nbsp;_bar_</pre>
</div>

<div class="case" data-name="nonASCII WS followed by element with leading nonASCII WS">
  <div class="input">foo&nbsp;<i>&nbsp;bar</i></div>
  <pre class="expected">foo&nbsp;&nbsp;_bar_</pre>
</div>

<div class="case" data-name="nonASCII WS followed by element with leading ASCII WS">
  <div class="input">foo&nbsp;<i> bar</i></div>
  <pre class="expected">foo&nbsp; _bar_</pre>
</div>

<div class="case" data-name="ASCII WS followed by element with leading nonASCII WS">
  <div class="input">foo <i>&nbsp;bar</i></div>
  <pre class="expected">foo &nbsp;_bar_</pre>
</div>

<!-- Behavior of `<code>` with CSS set as `white-space: pre-wrap;`, e.g. in GitLab -->
<div class="case" data-name="preformatted code with leading whitespace" data-options='{"preformattedCode": true}'>
  <div class="input">Four spaces <code>    make an indented code block in Markdown</code></div>
  <pre class="expected">Four spaces `    make an indented code block in Markdown`</pre>
</div>

<div class="case" data-name="preformatted code with trailing whitespace" data-options='{"preformattedCode": true}'>
  <div class="input"><code>A line break  </code> <b> note the spaces</b></div>
  <pre class="expected">`A line break  ` **note the spaces**</pre>
</div>

<div class="case" data-name="preformatted code tightly surrounded" data-options='{"preformattedCode": true}'>
  <div class="input"><b>tight</b><code>code</code><b>wrap</b></div>
  <pre class="expected">**tight**`code`**wrap**</pre>
</div>

<div class="case" data-name="preformatted code loosely surrounded" data-options='{"preformattedCode": true}'>
  <div class="input"><b>not so tight </b><code>code</code><b> wrap</b></div>
  <pre class="expected">**not so tight** `code` **wrap**</pre>
</div>

<!-- newlines become spaces + extra space must be added  -->
<div class="case" data-name="preformatted code with newlines" data-options='{"preformattedCode": true}'>
  <div class="input">
<code>

 nasty
code

</code>
  </div>
  <pre class="expected">`    nasty code   `</pre>
</div>

<!-- /TEST CASES -->

<script src="turndown-test.browser.js"></script>
</body>
</html>


================================================
FILE: test/internals-test.js
================================================
var test = require('tape').test
var rewire = require('rewire')
var turndownModule = rewire('../lib/turndown.cjs')

test('edge whitespace detection',function (t) {
  function ews (leadingAscii, leadingNonAscii, trailingNonAscii, trailingAscii) {
    return {
      leading: leadingAscii + leadingNonAscii,
      leadingAscii: leadingAscii,
      leadingNonAscii: leadingNonAscii,
      trailing: trailingNonAscii + trailingAscii,
      trailingNonAscii: trailingNonAscii,
      trailingAscii: trailingAscii
    }
  }
  var WS = '\r\n \t'
  var TEST_CASES = [
    [`${WS}HELLO WORLD${WS}`, ews(WS, '', '', WS)],
    [`${WS}H${WS}`, ews(WS, '', '', WS)],
    [`${WS}\xa0${WS}HELLO${WS}WORLD${WS}\xa0${WS}`, ews(WS, `\xa0${WS}`, `${WS}\xa0`, WS)],
    [`\xa0${WS}HELLO${WS}WORLD${WS}\xa0`, ews('', `\xa0${WS}`, `${WS}\xa0`, '')],
    [`\xa0${WS}\xa0`, ews('', `\xa0${WS}\xa0`, '', '')],
    [`${WS}\xa0${WS}`, ews(WS, `\xa0${WS}`, '', '')],
    [`${WS}\xa0`, ews(WS, `\xa0`, '', '')],
    [`HELLO WORLD`, ews('', '', '', '')],
    [``, ews('', '', '', '')],
    [`TEST${Array(32768).join(' ')}END`, ews('', '', '', '')], // performance check
  ]
  t.plan(TEST_CASES.length)
  t.timeoutAfter(300)
  var edgeWhitespace = turndownModule.__get__('edgeWhitespace')
  TEST_CASES.forEach(function (c) {
    t.deepEqual(edgeWhitespace(c[0]), c[1])
  })
})


================================================
FILE: test/turndown-test.js
================================================
var Attendant = require('turndown-attendant')
var TurndownService = require('../lib/turndown.cjs')

var attendant = new Attendant({
  file: __dirname + '/index.html',
  TurndownService: TurndownService
})
var test = attendant.test

attendant.run()

test('malformed documents', function (t) {
  t.plan(0)
  var turndownService = new TurndownService()
  turndownService.turndown('<HTML><head></head><BODY><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body onload=alert(document.cookie);></body></html>')
  t.end()
})

test('null input', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  t.throws(
    function () { turndownService.turndown(null) }, /null is not a string/
  )
})

test('undefined input', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  t.throws(
    function () { turndownService.turndown(void (0)) },
    /undefined is not a string/
  )
})

test('#addRule returns the instance', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  var rule = {
    filter: ['del', 's', 'strike'],
    replacement: function (content) {
      return '~~' + content + '~~'
    }
  }
  t.equal(turndownService.addRule('strikethrough', rule), turndownService)
})

test('#addRule adds the rule', function (t) {
  t.plan(2)
  var turndownService = new TurndownService()
  var rule = {
    filter: ['del', 's', 'strike'],
    replacement: function (content) {
      return '~~' + content + '~~'
    }
  }
  // Assert rules#add is called
  turndownService.rules.add = function (key, r) {
    t.equal(key, 'strikethrough')
    t.equal(rule, r)
  }
  turndownService.addRule('strikethrough', rule)
})

test('#use returns the instance for chaining', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  t.equal(turndownService.use(function plugin () {}), turndownService)
})

test('#use with a single plugin calls the fn with instance', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  function plugin (service) {
    t.equal(service, turndownService)
  }
  turndownService.use(plugin)
})

test('#use with multiple plugins calls each fn with instance', function (t) {
  t.plan(2)
  var turndownService = new TurndownService()
  function plugin1 (service) {
    t.equal(service, turndownService)
  }
  function plugin2 (service) {
    t.equal(service, turndownService)
  }
  turndownService.use([plugin1, plugin2])
})

test('#keep keeps elements as HTML', function (t) {
  t.plan(2)
  var turndownService = new TurndownService()
  var input = '<p>Hello <del>world</del><ins>World</ins></p>'

  // Without `.keep(['del', 'ins'])`
  t.equal(turndownService.turndown(input), 'Hello worldWorld')

  // With `.keep(['del', 'ins'])`
  turndownService.keep(['del', 'ins'])
  t.equal(
    turndownService.turndown('<p>Hello <del>world</del><ins>World</ins></p>'),
    'Hello <del>world</del><ins>World</ins>'
  )
})

test('#keep returns the TurndownService instance for chaining', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  t.equal(turndownService.keep(['del', 'ins']), turndownService)
})

test('keep rules are overridden by the standard rules', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  turndownService.keep('p')
  t.equal(turndownService.turndown('<p>Hello world</p>'), 'Hello world')
})

test('keeping elements that have a blank textContent but contain significant elements', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  turndownService.keep('figure')
  t.equal(
    turndownService.turndown('<figure><iframe src="http://example.com"></iframe></figure>'),
    '<figure><iframe src="http://example.com"></iframe></figure>'
  )
})

test('keepReplacement can be customised', function (t) {
  t.plan(1)
  var turndownService = new TurndownService({
    keepReplacement: function (content, node) {
      return '\n\n' + node.outerHTML + '\n\n'
    }
  })
  turndownService.keep(['del', 'ins'])
  t.equal(turndownService.turndown(
    '<p>Hello <del>world</del><ins>World</ins></p>'),
    'Hello \n\n<del>world</del>\n\n<ins>World</ins>'
  )
})

test('#remove removes elements', function (t) {
  t.plan(2)
  var turndownService = new TurndownService()
  var input = '<del>Please redact me</del>'

  // Without `.remove('del')`
  t.equal(turndownService.turndown(input), 'Please redact me')

  // With `.remove('del')`
  turndownService.remove('del')
  t.equal(turndownService.turndown(input), '')
})

test('#remove returns the TurndownService instance for chaining', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  t.equal(turndownService.remove(['del', 'ins']), turndownService)
})

test('remove elements are overridden by rules', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  turndownService.remove('p')
  t.equal(turndownService.turndown('<p>Hello world</p>'), 'Hello world')
})

test('remove elements are overridden by keep', function (t) {
  t.plan(1)
  var turndownService = new TurndownService()
  turndownService.keep(['del', 'ins'])
  turndownService.remove(['del', 'ins'])
  t.equal(turndownService.turndown(
    '<p>Hello <del>world</del><ins>World</ins></p>'),
    'Hello <del>world</del><ins>World</ins>'
  )
})
Download .txt
gitextract_kkzqji9c/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .tm_properties
├── LICENSE
├── README.md
├── SECURITY.md
├── config/
│   ├── rollup.config.browser.cjs.mjs
│   ├── rollup.config.browser.es.mjs
│   ├── rollup.config.browser.umd.mjs
│   ├── rollup.config.cjs.mjs
│   ├── rollup.config.es.mjs
│   ├── rollup.config.iife.mjs
│   ├── rollup.config.mjs
│   ├── rollup.config.test.mjs
│   └── rollup.config.umd.mjs
├── index.html
├── package.json
├── src/
│   ├── collapse-whitespace.js
│   ├── commonmark-rules.js
│   ├── html-parser.js
│   ├── node.js
│   ├── root-node.js
│   ├── rules.js
│   ├── turndown.js
│   └── utilities.js
└── test/
    ├── index.html
    ├── internals-test.js
    └── turndown-test.js
Download .txt
SYMBOL INDEX (44 symbols across 10 files)

FILE: src/collapse-whitespace.js
  function collapseWhitespace (line 33) | function collapseWhitespace (options) {
  function remove (line 108) | function remove (node) {
  function next (line 125) | function next (prev, current, isPre) {

FILE: src/commonmark-rules.js
  function cleanAttribute (line 258) | function cleanAttribute (attribute) {
  function escapeLinkDestination (line 262) | function escapeLinkDestination (destination) {
  function escapeLinkTitle (line 267) | function escapeLinkTitle (title) {

FILE: src/html-parser.js
  function canParseHTMLNatively (line 11) | function canParseHTMLNatively () {
  function createHTMLParser (line 27) | function createHTMLParser () {
  function shouldUseActiveX (line 58) | function shouldUseActiveX () {

FILE: src/node.js
  function Node (line 3) | function Node (node, options) {
  function isBlank (line 11) | function isBlank (node) {
  function flankingWhitespace (line 21) | function flankingWhitespace (node, options) {
  function edgeWhitespace (line 41) | function edgeWhitespace (string) {
  function isFlankedByWhitespace (line 53) | function isFlankedByWhitespace (side, node, options) {

FILE: src/root-node.js
  function RootNode (line 5) | function RootNode (input, options) {
  function htmlParser (line 31) | function htmlParser () {
  function isPreOrCode (line 36) | function isPreOrCode (node) {
  function normalizePre (line 40) | function normalizePre (root) {

FILE: src/rules.js
  function Rules (line 5) | function Rules (options) {
  function findRule (line 61) | function findRule (rules, node, options) {
  function filterValue (line 69) | function filterValue (rule, node, options) {

FILE: src/turndown.js
  function TurndownService (line 8) | function TurndownService (options) {
  function process (line 140) | function process (parentNode) {
  function postProcess (line 164) | function postProcess (output) {
  function replacementForNode (line 183) | function replacementForNode (node) {
  function join (line 204) | function join (output, replacement) {
  function canConvert (line 221) | function canConvert (input) {

FILE: src/utilities.js
  function extend (line 1) | function extend (destination) {
  function repeat (line 11) | function repeat (character, count) {
  function trimLeadingNewlines (line 15) | function trimLeadingNewlines (string) {
  function trimTrailingNewlines (line 19) | function trimTrailingNewlines (string) {
  function trimNewlines (line 26) | function trimNewlines (string) {
  function isBlock (line 39) | function isBlock (node) {
  function isVoid (line 48) | function isVoid (node) {
  function hasVoid (line 52) | function hasVoid (node) {
  function isMeaningfulWhenBlank (line 61) | function isMeaningfulWhenBlank (node) {
  function hasMeaningfulWhenBlank (line 65) | function hasMeaningfulWhenBlank (node) {
  function is (line 69) | function is (node, tagNames) {
  function has (line 73) | function has (node, tagNames) {
  function escapeMarkdown (line 98) | function escapeMarkdown (string) {

FILE: test/internals-test.js
  function ews (line 6) | function ews (leadingAscii, leadingNonAscii, trailingNonAscii, trailingA...

FILE: test/turndown-test.js
  function plugin (line 74) | function plugin (service) {
  function plugin1 (line 83) | function plugin1 (service) {
  function plugin2 (line 86) | function plugin2 (service) {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (100K chars).
[
  {
    "path": ".github/workflows/test.yml",
    "chars": 541,
    "preview": "name: Test\n\non: \n  push:\n    branches: [main]\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  test:\n    runs-on: ubuntu-la"
  },
  {
    "path": ".gitignore",
    "chars": 53,
    "preview": "dist\nlib\nnode_modules\nnpm-debug.log\ntest/*browser.js\n"
  },
  {
    "path": ".tm_properties",
    "chars": 57,
    "preview": "[test/index.html]\nscopeAttributes = attr.keep-whitespace\n"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2017 Dom Christie\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 9466,
    "preview": "# Turndown\n\nConvert HTML into Markdown with JavaScript.\n\nSee it in action [online](https://mixmark-io.github.io/turndown"
  },
  {
    "path": "SECURITY.md",
    "chars": 1630,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          | Remark |\n| ------- | ------------------ | ---"
  },
  {
    "path": "config/rollup.config.browser.cjs.mjs",
    "chars": 182,
    "preview": "import config from './rollup.config.mjs'\n\nexport default config({\n  output: {\n    file: 'lib/turndown.browser.cjs.js',\n "
  },
  {
    "path": "config/rollup.config.browser.es.mjs",
    "chars": 159,
    "preview": "import config from './rollup.config.mjs'\n\nexport default config({\n  output: {\n    file: 'lib/turndown.browser.es.js',\n  "
  },
  {
    "path": "config/rollup.config.browser.umd.mjs",
    "chars": 190,
    "preview": "import config from './rollup.config.mjs'\n\nexport default config({\n  output: {\n    file: 'lib/turndown.browser.umd.js',\n "
  },
  {
    "path": "config/rollup.config.cjs.mjs",
    "chars": 175,
    "preview": "import config from './rollup.config.mjs'\n\nexport default config({\n  output: {\n    file: 'lib/turndown.cjs.js',\n    forma"
  },
  {
    "path": "config/rollup.config.es.mjs",
    "chars": 152,
    "preview": "import config from './rollup.config.mjs'\n\nexport default config({\n  output: {\n    file: 'lib/turndown.es.js',\n    format"
  },
  {
    "path": "config/rollup.config.iife.mjs",
    "chars": 180,
    "preview": "import config from './rollup.config.mjs'\n\nexport default config({\n  output: {\n    file: 'dist/turndown.js',\n    format: "
  },
  {
    "path": "config/rollup.config.mjs",
    "chars": 677,
    "preview": "import commonjs from '@rollup/plugin-commonjs'\nimport replace from '@rollup/plugin-replace'\nimport resolve from '@rollup"
  },
  {
    "path": "config/rollup.config.test.mjs",
    "chars": 376,
    "preview": "import commonjs from '@rollup/plugin-commonjs'\nimport resolve from '@rollup/plugin-node-resolve'\n\nexport default {\n  inp"
  },
  {
    "path": "config/rollup.config.umd.mjs",
    "chars": 165,
    "preview": "import config from './rollup.config.mjs'\n\nexport default config({\n  output: {\n    file: 'lib/turndown.umd.js',\n    forma"
  },
  {
    "path": "index.html",
    "chars": 6461,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Turndown Demo</title>\n<meta name=\"viewport\" conten"
  },
  {
    "path": "package.json",
    "chars": 2003,
    "preview": "{\n  \"name\": \"turndown\",\n  \"description\": \"A library that converts HTML to Markdown\",\n  \"version\": \"7.2.2\",\n  \"author\": \""
  },
  {
    "path": "src/collapse-whitespace.js",
    "chars": 3885,
    "preview": "/**\n * The collapseWhitespace function is adapted from collapse-whitespace\n * by Luc Thevenard.\n *\n * The MIT License (M"
  },
  {
    "path": "src/commonmark-rules.js",
    "chars": 7059,
    "preview": "import { escapeMarkdown, repeat, trimNewlines } from './utilities'\n\nconst rules = {}\n\nrules.paragraph = {\n  filter: 'p',"
  },
  {
    "path": "src/html-parser.js",
    "chars": 1609,
    "preview": "/*\n * Set up window for Node.js\n */\n\nconst root = (typeof window !== 'undefined' ? window : {})\n\n/*\n * Parsing HTML stri"
  },
  {
    "path": "src/node.js",
    "chars": 2146,
    "preview": "import { isBlock, isVoid, hasVoid, isMeaningfulWhenBlank, hasMeaningfulWhenBlank } from './utilities'\n\nexport default fu"
  },
  {
    "path": "src/root-node.js",
    "chars": 1451,
    "preview": "import collapseWhitespace from './collapse-whitespace'\nimport HTMLParser from './html-parser'\nimport { isBlock, isVoid }"
  },
  {
    "path": "src/rules.js",
    "chars": 1914,
    "preview": "/**\n * Manages a collection of rules used to convert HTML to Markdown\n */\n\nexport default function Rules (options) {\n  t"
  },
  {
    "path": "src/turndown.js",
    "chars": 6038,
    "preview": "import COMMONMARK_RULES from './commonmark-rules'\nimport Rules from './rules'\nimport { escapeMarkdown, extend, trimLeadi"
  },
  {
    "path": "src/utilities.js",
    "chars": 2682,
    "preview": "export function extend (destination) {\n  for (let i = 1; i < arguments.length; i++) {\n    const source = arguments[i]\n  "
  },
  {
    "path": "test/index.html",
    "chars": 36048,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>turndown test runner</title>\n  <link rel=\"styl"
  },
  {
    "path": "test/internals-test.js",
    "chars": 1344,
    "preview": "var test = require('tape').test\nvar rewire = require('rewire')\nvar turndownModule = rewire('../lib/turndown.cjs')\n\ntest("
  },
  {
    "path": "test/turndown-test.js",
    "chars": 5350,
    "preview": "var Attendant = require('turndown-attendant')\nvar TurndownService = require('../lib/turndown.cjs')\n\nvar attendant = new "
  }
]

About this extraction

This page contains the full source code of the mixmark-io/turndown GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (90.9 KB), approximately 26.6k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!