Returns given comment HTML as plaintext.

    Converts all HTML tags and entities within 4chan comments
    into human-readable text equivalents.
510448
def clean_comment_body(body):
    
    body = _parser.unescape(body)
    body = re.sub(r'<a [^>]+>(.+?)</a>', r'\1', body)
    body = body.replace('<br>', '\n')
    body = re.sub(r'<.+?>', '', body)
    return body
1012417
def get_as_text(self):
        
        d = {
            'user': self.user or self.name,
            'date': self.post_date,
            'comment': self.text,
            'domain': self.site.domain,
            'url': self.get_absolute_url()
        }
        return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
1144159
def get_text(html_content, display_images=False, deduplicate_captions=False, display_links=False):
    '''
    
    '''
    html_content = html_content.strip()
    if not html_content:
        return ""

    # strip XML declaration, if necessary
    if html_content.startswith('<?xml '):
        html_content = RE_STRIP_XML_DECLARATION.sub('', html_content, count=1)

    html_tree = fromstring(html_content)
    parser = Inscriptis(html_tree, display_images=display_images, deduplicate_captions=deduplicate_captions, display_links=display_links)
    return parser.get_text()
885154
def parse_comment(comment):
    
    names, vals = [], []
    pieces = comment.split('=')
    for i, piece in enumerate(pieces):
        if i == 0:  # first line
            names.append(piece.strip())
        elif i == len(pieces) - 1:  # last line
            vals.append(ast.literal_eval(piece))
        else:
            val, name = piece.rsplit(',', 1)
            vals.append(ast.literal_eval(val))
            names.append(name.strip())
    return list(zip(names, vals))
186381
function formatComment(comment) {
  if (!comment) {
    return undefined;
  }
  const parts = comment.trim().split('\n');
  if (parts.length === 1 && parts[0].indexOf('*') !== 0) {
    return parts[0];
  }
  return parts.reduce((previousValue, currentValue) => {
    // newlines in the middle of the comment should stay to achieve:
    // multiline comments entered by user drive unchanged from JDL
    // studio to generated domain class
    let delimiter = '';
    if (previousValue !== '') {
      delimiter = '\n';
    }
    return previousValue.concat(delimiter, currentValue.trim().replace(/[*]*\s*/, ''));
  }, '');
}
579236
function parseMLComment(css) {
    var start = pos;

    // Read the string until we meet `*/`.
    // Since we already know first 2 characters (`/*`), start reading
    // from `pos + 2`:
    for (pos += 2; pos < css.length; pos++) {
      if (css.charAt(pos) === '*' && css.charAt(pos + 1) === '/') {
        pos++;
        break;
      }
    }

    // Add full comment (including `/*` and `*/`) to the list of tokens:
    var comment = css.substring(start, pos + 1);
    pushToken(TokenType.CommentML, comment, col);

    var newlines = comment.split('\n');
    if (newlines.length > 1) {
      ln += newlines.length - 1;
      col = newlines[newlines.length - 1].length;
    } else {
      col += (pos - start);
    }
  }
567597
def parse(self, text, html=True):
        ''''''
        self._urls = []
        self._users = []
        self._lists = []
        self._tags = []

        reply = REPLY_REGEX.match(text)
        reply = reply.groups(0)[0] if reply is not None else None

        parsed_html = self._html(text) if html else self._text(text)
        return ParseResult(self._urls, self._users, reply,
                           self._lists, self._tags, parsed_html)
653356
function htmlDecode(input) {
  if (input === " ") return input;
  const doc = new DOMParser().parseFromString(input.replace(/<[^>]+>/g, ""), "text/html");
  return doc.documentElement.textContent;
}
292688
def decode_html_entities(s):
    
    parser = HTMLParser.HTMLParser()

    def unesc(m):
        return parser.unescape(m.group())
    return re.sub(r'(&[^;]+;)', unesc, ensure_unicode(s))
124874
def process_ie_conditional_comments (out)
      out.gsub /(<!--\[[^\]]+\]>([\s\S]*?)<!\[[^\]]+\]-->)\s*/ do
        m = $1
        comment = $2
        comment_compressed = Html.new.press(comment)
        m.gsub!(comment, comment_compressed)
        reserve m
      end
    end
577831
def make_html_patterns():
    
    tags = any("builtin", [r"<", r"[\?/]?>", r"(?<=<).*?(?=[ >])"])
    keywords = any("keyword", [r" [\w:-]*?(?==)"])
    string = any("string", [r'".*?"'])
    comment = any("comment", [r"<!--.*?-->"])
    multiline_comment_start = any("multiline_comment_start", [r"<!--"])
    multiline_comment_end = any("multiline_comment_end", [r"-->"])
    return "|".join([comment, multiline_comment_start,
                     multiline_comment_end, tags, keywords, string])
16919
function extractToText(strings)
			{
				var $htmlContent, $htmlPattern, $htmlReplace;

				// first remove to unnecessary gaps
				$htmlContent = strings.replace(/\n/gim,'').replace(/\r/gim,'').replace(/\t/gim,'').replace(/&nbsp;/gim,' ');

				$htmlPattern =  [
					/\<span(|\s+.*?)><span(|\s+.*?)>(.*?)<\/span><\/span>/gim, // trim nested spans
					/<(\w*[^p])\s*[^\/>]*>\s*<\/\1>/gim, // remove empty or white-spaces tags (ignore paragraphs (<p>) and breaks (<br>))
					/\<div(|\s+.*?)>(.*?)\<\/div>/gim, // convert div to p
					/\<strong(|\s+.*?)>(.*?)\<\/strong>/gim, // convert strong to b
					/\<em(|\s+.*?)>(.*?)\<\/em>/gim // convert em to i
				];

				$htmlReplace = [
					'<span$2>$3</span>',
					'',
					'<p$1>$2</p>',
					'<b$1>$2</b>',
					'<i$1>$2</i>'
				];
				
				// repeat the cleaning process 5 times
				for(c=0; c<5; c++)
				{
					// create loop as the number of pattern
					for(var i = 0; i < $htmlPattern.length; i++)
					{
						$htmlContent = $htmlContent.replace($htmlPattern[i], $htmlReplace[i]);
					}
				}

				// if paragraph is false (<p>), convert <p> to <br>
				if(!vars.p)
					$htmlContent = $htmlContent.replace(/\<p(|\s+.*?)>(.*?)\<\/p>/ig, '<br/>$2');

				// if break is false (<br>), convert <br> to <p>
				if(!vars.br)
				{
					$htmlPattern =  [
						/\<br>(.*?)/ig,
						/\<br\/>(.*?)/ig
					];

					$htmlReplace = [
						'<p>$1</p>',
						'<p>$1</p>'
					];

					// create loop as the number of pattern (for breaks)
					for (var i = 0; i < $htmlPattern.length; i++) {
						$htmlContent = $htmlContent.replace($htmlPattern[i], $htmlReplace[i]);
					}
				}

				// if paragraph and break is false (<p> && <br>), convert <p> to <div>
				if(!vars.p && !vars.br)
					$htmlContent = $htmlContent.replace(/\<p>(.*?)\<\/p>/ig, '<div>$1</div>');

				return $htmlContent;
			}
988980
function processString(content = '', options = defaultOptions) {

    const comments = []
    const preservedTokens = []

    let pattern

    content = extractDataUrls(content, preservedTokens)
    content = convertRelativeUrls(content, options, preservedTokens)
    content = collectComments(content, comments)

    // preserve strings so their content doesn't get accidentally minified
    pattern = /("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/g
    content = content.replace(pattern, token => {
        const quote = token.substring(0, 1)
        token = token.slice(1, -1)
        // maybe the string contains a comment-like substring or more? put'em back then
        if (token.indexOf('___PRESERVE_CANDIDATE_COMMENT_') >= 0) {
            for (let i = 0, len = comments.length; i < len; i += 1) {
                token = token.replace('___PRESERVE_CANDIDATE_COMMENT_' + i + '___', comments[i])
            }
        }
        // minify alpha opacity in filter strings
        token = token.replace(/progid:DXImageTransform.Microsoft.Alpha\(Opacity=/gi, 'alpha(opacity=')
        preservedTokens.push(token)
        return quote + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___' + quote
    })

    // strings are safe, now wrestle the comments
    for (let i = 0, len = comments.length; i < len; i += 1) {

        let token = comments[i]
        let placeholder = '___PRESERVE_CANDIDATE_COMMENT_' + i + '___'

        // ! in the first position of the comment means preserve
        // so push to the preserved tokens keeping the !
        if (token.charAt(0) === '!') {
            if (options.cuteComments) {
                preservedTokens.push(token.substring(1).replace(/\r\n/g, '\n'))
            } else if (options.uglyComments) {
                preservedTokens.push(token.substring(1).replace(/[\r\n]/g, ''))
            } else {
                preservedTokens.push(token)
            }
            content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
            continue
        }

        // \ in the last position looks like hack for Mac/IE5
        // shorten that to /*\*/ and the next one to /**/
        if (token.charAt(token.length - 1) === '\\') {
            preservedTokens.push('\\')
            content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
            i = i + 1 // attn: advancing the loop
            preservedTokens.push('')
            content = content.replace(
                '___PRESERVE_CANDIDATE_COMMENT_' + i + '___',
                ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___'
            )
            continue
        }

        // keep empty comments after child selectors (IE7 hack)
        // e.g. html >/**/ body
        if (token.length === 0) {
            let startIndex = content.indexOf(placeholder)
            if (startIndex > 2) {
                if (content.charAt(startIndex - 3) === '>') {
                    preservedTokens.push('')
                    content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
                }
            }
        }

        // in all other cases kill the comment
        content = content.replace(`/*${placeholder}*/`, '')
    }

    // parse simple @variables blocks and remove them
    if (options.expandVars) {
        const vars = {}
        pattern = /@variables\s*\{\s*([^\}]+)\s*\}/g
        content = content.replace(pattern, (_, f1) => {
            pattern = /\s*([a-z0-9\-]+)\s*:\s*([^;\}]+)\s*/gi
            f1.replace(pattern, (_, f1, f2) => {
                if (f1 && f2) {
                    vars[f1] = f2
                }
                return ''
            })
            return ''
        })

        // replace var(x) with the value of x
        pattern = /var\s*\(\s*([^\)]+)\s*\)/g
        content = content.replace(pattern, (_, f1) => {
            return vars[f1] || 'none'
        })
    }

    // normalize all whitespace strings to single spaces. Easier to work with that way.
    content = content.replace(/\s+/g, ' ')

    // preserve formulas in calc() before removing spaces
    pattern = /calc\(([^;}]*)\)/g
    content = content.replace(pattern, (_, f1) => {
        preservedTokens.push(
            'calc(' +
            f1.replace(/(^\s*|\s*$)/g, '')
                .replace(/\( /g, '(')
                .replace(/ \)/g, ')') +
            ')'
        )
        return ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___'
    })

    // preserve matrix
    pattern = /\s*filter:\s*progid:DXImageTransform.Microsoft.Matrix\(([^\)]+)\);/g
    content = content.replace(pattern, (_, f1) => {
        preservedTokens.push(f1)
        return 'filter:progid:DXImageTransform.Microsoft.Matrix(' + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___);'
    })

    // remove the spaces before the things that should not have spaces before them.
    // but, be careful not to turn 'p :link {...}' into 'p:link{...}'
    // swap out any pseudo-class colons with the token, and then swap back.
    pattern = /(^|\})(([^\{:])+:)+([^\{]*\{)/g
    content = content.replace(pattern, token => token.replace(/:/g, '___PSEUDOCLASSCOLON___'))

    // remove spaces before the things that should not have spaces before them.
    content = content.replace(/\s+([!{};:>+\(\)\],])/g, '$1')

    // restore spaces for !important
    content = content.replace(/!important/g, ' !important')

    // bring back the colon
    content = content.replace(/___PSEUDOCLASSCOLON___/g, ':')

    // preserve 0 followed by a time unit for properties using time units
    pattern = /\s*(animation|animation-delay|animation-duration|transition|transition-delay|transition-duration):\s*([^;}]+)/gi
    content = content.replace(pattern, (_, f1, f2) => {

        f2 = f2.replace(/(^|\D)0?\.?0(m?s)/gi, (_, g1, g2) => {
            preservedTokens.push('0' + g2)
            return g1 + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___'
        })

        return f1 + ':' + f2
    })

    // preserve unit for flex-basis within flex and flex-basis (ie10 bug)
    pattern = /\s*(flex|flex-basis):\s*([^;}]+)/gi
    content = content.replace(pattern, (_, f1, f2) => {
        let f2b = f2.split(/\s+/)
        preservedTokens.push(f2b.pop())
        f2b.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
        f2b = f2b.join(' ')
        return `${f1}:${f2b}`
    })

    // preserve 0% in hsl and hsla color definitions
    content = content.replace(/(hsla?)\(([^)]+)\)/g, (_, f1, f2) => {
        var f0 = []
        f2.split(',').forEach(part => {
            part = part.replace(/(^\s+|\s+$)/g, '')
            if (part === '0%') {
                preservedTokens.push('0%')
                f0.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + '___')
            } else {
                f0.push(part)
            }
        })
        return f1 + '(' + f0.join(',') + ')'
    })

    // preserve 0 followed by unit in keyframes steps (WIP)
    content = keyframes(content, preservedTokens)

    // retain space for special IE6 cases
    content = content.replace(/:first-(line|letter)(\{|,)/gi, (_, f1, f2) => ':first-' + f1.toLowerCase() + ' ' + f2)

    // newlines before and after the end of a preserved comment
    if (options.cuteComments) {
        content = content.replace(/\s*\/\*/g, '___PRESERVED_NEWLINE___/*')
        content = content.replace(/\*\/\s*/g, '*/___PRESERVED_NEWLINE___')
        // no space after the end of a preserved comment
    } else {
        content = content.replace(/\*\/\s*/g, '*/')
    }

    // If there are multiple @charset directives, push them to the top of the file.
    pattern = /^(.*)(@charset)( "[^"]*";)/gi
    content = content.replace(pattern, (_, f1, f2, f3) => f2.toLowerCase() + f3 + f1)

    // When all @charset are at the top, remove the second and after (as they are completely ignored).
    pattern = /^((\s*)(@charset)( [^;]+;\s*))+/gi
    content = content.replace(pattern, (_, __, f2, f3, f4) => f2 + f3.toLowerCase() + f4)

    // lowercase some popular @directives (@charset is done right above)
    pattern = /@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/gi
    content = content.replace(pattern, (_, f1) => '@' + f1.toLowerCase())

    // lowercase some more common pseudo-elements
    pattern = /:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/gi
    content = content.replace(pattern, (_, f1) => ':' + f1.toLowerCase())

    // if there is a @charset, then only allow one, and push to the top of the file.
    content = content.replace(/^(.*)(@charset \"[^\"]*\";)/g, '$2$1')
    content = content.replace(/^(\s*@charset [^;]+;\s*)+/g, '$1')

    // lowercase some more common functions
    pattern = /:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?any)\(/gi
    content = content.replace(pattern, (_, f1) => ':' + f1.toLowerCase() + '(')

    // lower case some common function that can be values
    // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
    pattern = /([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/gi
    content = content.replace(pattern, (_, f1, f2) => f1 + f2.toLowerCase())

    // put the space back in some cases, to support stuff like
    // @media screen and (-webkit-min-device-pixel-ratio:0){
    content = content.replace(/\band\(/gi, 'and (')

    // remove the spaces after the things that should not have spaces after them.
    content = content.replace(/([!{}:;>+\(\[,])\s+/g, '$1')

    // remove unnecessary semicolons
    content = content.replace(/;+\}/g, '}')

    // replace 0(px,em,%) with 0.
    content = content.replace(/(^|[^.0-9\\])(?:0?\.)?0(?:ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%)/gi, '$10')

    // Replace x.0(px,em,%) with x(px,em,%).
    content = content.replace(/([0-9])\.0(ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%| |;)/gi, '$1$2')

    // replace 0 0 0 0; with 0.
    content = content.replace(/:0 0 0 0(;|\})/g, ':0$1')
    content = content.replace(/:0 0 0(;|\})/g, ':0$1')
    content = content.replace(/:0 0(;|\})/g, ':0$1')

    // replace background-position:0; with background-position:0 0;
    // same for transform-origin and box-shadow
    pattern = /(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin|box-shadow):0(;|\})/gi
    content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ':0 0' + f2)

    // replace 0.6 to .6, but only when preceded by : or a white-space
    content = content.replace(/(:|\s)0+\.(\d+)/g, '$1.$2')

    // shorten colors from rgb(51,102,153) to #336699
    // this makes it more likely that it'll get further compressed in the next step.
    pattern = /rgb\s*\(\s*([0-9,\s]+)\s*\)/gi
    content = content.replace(pattern, (_, f1) => {
        const rgbcolors = f1.split(',')
        let hexcolor = '#'
        for (let i = 0; i < rgbcolors.length; i += 1) {
            let val = parseInt(rgbcolors[i], 10)
            if (val < 16) {
                hexcolor += '0'
            }
            if (val > 255) {
                val = 255
            }
            hexcolor += val.toString(16)
        }
        return hexcolor
    })

    // Shorten colors from #AABBCC to #ABC.
    content = compressHexColors(content)

    // Replace #f00 -> red
    content = content.replace(/(:|\s)(#f00)(;|})/g, '$1red$3')

    // Replace other short color keywords
    content = content.replace(/(:|\s)(#000080)(;|})/g, '$1navy$3')
    content = content.replace(/(:|\s)(#808080)(;|})/g, '$1gray$3')
    content = content.replace(/(:|\s)(#808000)(;|})/g, '$1olive$3')
    content = content.replace(/(:|\s)(#800080)(;|})/g, '$1purple$3')
    content = content.replace(/(:|\s)(#c0c0c0)(;|})/g, '$1silver$3')
    content = content.replace(/(:|\s)(#008080)(;|})/g, '$1teal$3')
    content = content.replace(/(:|\s)(#ffa500)(;|})/g, '$1orange$3')
    content = content.replace(/(:|\s)(#800000)(;|})/g, '$1maroon$3')

    // border: none -> border:0
    pattern = /(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|\})/gi
    content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ':0' + f2)

    // shorter opacity IE filter
    content = content.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, 'alpha(opacity=')

    // Find a fraction that is used for Opera's -o-device-pixel-ratio query
    // Add token to add the '\' back in later
    content = content.replace(/\(([\-A-Za-z]+):([0-9]+)\/([0-9]+)\)/g, '($1:$2___QUERY_FRACTION___$3)')

    // remove empty rules.
    content = content.replace(/[^\};\{\/]+\{\}/g, '')

    // Add '\' back to fix Opera -o-device-pixel-ratio query
    content = content.replace(/___QUERY_FRACTION___/g, '/')

    // some source control tools don't like it when files containing lines longer
    // than, say 8000 characters, are checked in. The linebreak option is used in
    // that case to split long lines after a specific column.
    if (options.maxLineLen > 0) {
        const lines = []
        let line = []
        for (let i = 0, len = content.length; i < len; i += 1) {
            let ch = content.charAt(i)
            line.push(ch)
            if (ch === '}' && line.length > options.maxLineLen) {
                lines.push(line.join(''))
                line = []
            }
        }
        if (line.length) {
            lines.push(line.join(''))
        }

        content = lines.join('\n')
    }

    // replace multiple semi-colons in a row by a single one
    // see SF bug #1980989
    content = content.replace(/;;+/g, ';')

    // trim the final string (for any leading or trailing white spaces)
    content = content.replace(/(^\s*|\s*$)/g, '')

    // restore preserved tokens
    for (let i = preservedTokens.length - 1; i >= 0; i--) {
        content = content.replace(___PRESERVED_TOKEN_ + i + '___', preservedTokens[i], 'g')
    }

    // restore preserved newlines
    content = content.replace(/___PRESERVED_NEWLINE___/g, '\n')

    // return
    return content
}
1217096
def parse(self, text, as_html=True):
        
        if not text:
            return text
        entity_text = self.get_text(text)

        if self.type == MessageEntityType.BOLD:
            if as_html:
                return markdown.hbold(entity_text)
            return markdown.bold(entity_text)
        elif self.type == MessageEntityType.ITALIC:
            if as_html:
                return markdown.hitalic(entity_text)
            return markdown.italic(entity_text)
        elif self.type == MessageEntityType.PRE:
            if as_html:
                return markdown.hpre(entity_text)
            return markdown.pre(entity_text)
        elif self.type == MessageEntityType.CODE:
            if as_html:
                return markdown.hcode(entity_text)
            return markdown.code(entity_text)
        elif self.type == MessageEntityType.URL:
            if as_html:
                return markdown.hlink(entity_text, entity_text)
            return markdown.link(entity_text, entity_text)
        elif self.type == MessageEntityType.TEXT_LINK:
            if as_html:
                return markdown.hlink(entity_text, self.url)
            return markdown.link(entity_text, self.url)
        elif self.type == MessageEntityType.TEXT_MENTION and self.user:
            return self.user.get_mention(entity_text)
        return entity_text
1267475
def _comment_type_from_line(line):
    
    regex = re.compile(r"^{0}".format(_ALL_COMMENT))
    match = regex.match(line)
    if match:
        return "{0} ".format(line[match.start():match.end()])

    raise RuntimeError("Unable to find comment header for {0}".format(line))
982863
function commentStrip(string) {
  // Remove commented lines
  string = string.replace(RE_COMMENT_SINGLE_LINE, '');
  string = string.replace(RE_COMMENT_MULTI_LINES, '');
  return string;
}
1273043
def stringify_comment_array comments
      ctxt = ''
      num = nil
      started = false
      last_line = nil
      comments.each { |l|
        # Trim the comment and minimum leading whitespace
        p = l.text.gsub(/^#+/, '')
        if num.nil? and !p.strip.empty?
          num = p.index(/[^ ]/)
          started = true
        elsif started and !p.strip.empty?
          cur = p.index(/[^ ]/)
          num = cur if cur < num
        end
        # Include blank lines between comments
        ctxt += ("\n" * (l.loc.first_line - last_line - 1)) unless last_line.nil? || l.loc.first_line - last_line <= 0
        ctxt += "#{p[num..-1]}\n" if started
        last_line = l.loc.last_line if last_line.nil? || l.loc.last_line > last_line
      }
      ctxt
    end