dennissuitters / summernote-cleaner Goto Github PK
View Code? Open in Web Editor NEWPlugin for Summernote that adds a Button and/or Paste functionality for cleaning MS Word Crud from editor text
License: MIT License
Plugin for Summernote that adds a Button and/or Paste functionality for cleaning MS Word Crud from editor text
License: MIT License
I executed as in the example in the readme and it did not work correctly, of every form and configuration that I use it always removes all the elements when I am going to paste a content.
I put keepHtml to true and even then the tags defined in keepOnlyTags are removed when pasting content.
I am using version 0.8.7 with webpack.
require('summernote');
require('summernote-cleaner')
$('.summernote').summernote({
toolbar: [
['style', ['bold', 'italic', 'underline']],
['misc', ['undo','redo','fullscreen']],
],
disableDragAndDrop: true,
disableResizeEditor: true,
cleaner:{
notTime: 2400,
action: 'paste',
newline: '<br>',
notStyle: 'position:absolute;top:0;left:0;right:0',
keepHtml: true,
keepOnlyTags: ['<p>', '<br>', '<ul>', '<li>', '<b>', '<strong>','<i>', '<a>'],
keepClasses: false,
badTags: ['table','style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html'],
badAttributes: ['style', 'start']
}
});
Thanks!
summernote Verison: v0.8.11
Your plugin didnt keep the tags, it will paste all of the text inside the
...........
I am not familiar with javascript
i think this part code had some logical issue
if (allowedTags.length > 0) {
allowedTags = (((allowedTags||'') + '').toLowerCase().match(/<[a-z][a-z0-9]>/g) || []).join('');
var tags = /</?([a-z][a-z0-9])\b[^>]*>/gi;
out = out.replace(tags, function($0, $1) {
return allowedTags.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''
});
}
I have three distinct summernote's instances. When I try to copy from one to paste into another the code throws "Uncaught TypeError: Cannot read property 'length' of undefined" (summernote-cleaner.js:60).
Call stack:
at cleanText (summernote-cleaner.js:60)
at HTMLDivElement.summernote.paste (summernote-cleaner.js:120)
at HTMLDivElement.dispatch (jquery.min.js:5)
at HTMLDivElement.v.handle (jquery.min.js:5)
at Object.trigger (jquery.min.js:5)
at HTMLDivElement. (jquery.min.js:5)
at Function.each (jquery.min.js:4)
at init.each (jquery.min.js:4)
at init.trigger (jquery.min.js:5)
at Context.triggerEvent (summernote.min.js:3)
I'll try to fix it here but any help would be nice, hehe.
currently i have 2 textarea inputs "one for english, one for french" and i have the option where the user can write into any of them "not required to fill both inputs",
so when u click the btn it will paste the text into the 2 inputs, this ofcourse doesn't happen with the paste option.
This works nice, but I prefer it to implement it together with the onPaste filter to remove HTML formatting as well, such as font faces.
$('.wysiwyg').summernote({
toolbar: [
..
['cleaner',['cleaner']]
],
cleaner:{
el:'.summernote', // Element ID or Class used to Initialise Summernote.
notTime:2400, // Time to display Notifications.
action:'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
icon:'<i class="fa fa-file-word-o"> Clean makeup</i>'
},
callbacks: {
onPaste: function(e) {
var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');
e.preventDefault();
document.execCommand('insertText', false, bufferText);
}
}
});
If this could be added as an option, would be nice.
I also should note that the onPaste filter alone filters Word processor markup, but it brakes pasting functionality in Internet Explorer, so that's why I was happy to find this plugin!
Hi,
Firstly, thank you so much for your effort in making Summernote awesome.
I am currently having issues with the action button options. Anytime I click the button, it wipes all content. I have tried it with several options...even outside my app just to be sure there is no conflict with other JS on the app.
Also, the newLine option is not working.. the editor still use
</ p>and I don't think this:
var nL=/(\n)+/g;
out=out.replace(nL,nlO);
is not replacing anything.
Pls what can I do?
I am testing this in latest Google Chrome
to re-produce "paste & btn"
1- copy some text
2- paste into the textarea
3- try the paste again.
expect
1- you should have duplicated content, instead the plugin will replace everything in the input with the new content only.
suggestion
1- the plugin should not replace the old content , instead it should append to.
Replace
var text=$($note.summernote('code')).text();
with
var text=cleanText($note.summernote('code'));
This is my summernote-clearner code
cleaner:{
notTime: 2400, // Time to display Notifications.
action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
newline: '<br>', // Summernote's default is to use '<p><br></p>'
notStyle: 'position:absolute;top:0;left:0;right:0', // Position of Notification
icon: '<i class="note-icon">[Your Button]</i>',
keepHtml: true, // Remove all Html formats
keepOnlyTags: ['<p>', '<br>', '<ul>', '<li>', '<b>', '<strong>','<i>', '<a>'], // If keepHtml is true, remove all tags except these
keepClasses: false, // Remove Classes
badTags: ['style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html'], // Remove full tags with contents
badAttributes: ['style', 'start'] // Remove attributes from remaining tags
}
This work fine when i copy from html and paste it to summernote editor. But if i copy from doc it does not event paste anythong but shows msg 'text is cleaned'.
If i remove summernote-cleaner than i can copy paste from doc but I want it form both doc and html.
Any help would be appreciated. Thanks
This does great at cleaning up all the extra class defs and such, but what about word 'Fancy' quotes / apostrophes?
I use summernote in an environment with multiple instances of summernote, which sometimes are beeing called multiple times.
Here's my script:
`require('summernote');
require('summernote-cleaner/summernote-cleaner');
//initialize Summernote
window.initializeSummernote = function initializeSummernote() {
$('.summernote').each(function () {
let maxlen = $(this).attr('data-max-length');
if(maxlen > 0){
$(this).summernote({
height: 100,
toolbar:[
['style', ['bold', 'italic', 'underline', 'clear']],
['para', ['ul']]
],
cleaner:{
limitChars: maxlen,
limitDisplay: 'both',
limitStop: true
}
})
} else {
$(this).summernote({
height: 100,
toolbar:[
['style', ['bold', 'italic', 'underline', 'clear']],
['para', ['ul']]
]
})
}
});
};`
Problem: It adds the counter to the first editor (which shouldn't have one) and the ones which I want to limit don't work at all.
Using npm summernote 0.8.9
There should be the option for filters like in sanitize-html.
I think summernote-cleaner is a great companion with sanitize-html since they can both work side by side, one on the front and the other on the back-end.
Sorry,
I'm not sure how to title this or really describe the issue other than to walk you through the steps to reproduce it. Let's say I type the following into the WYSIWYG editor:
This is a test of the cleaner...
Can I copy a <b>bolded</b> text without issue?
If I then highlight the text, copy it, clear the editor and paste it back in, without your plugin I'd expect to see the text pasted in exactly as I copied it. This it is, however upon activating your plugin, after being informed that my text has been cleaned I now see the following:
This is a test of the cleaner...
Can I copy a bolded text without issue?
Obviously this is NOT what I'd expect. I'd expect to have the initial line either remain bold or be stripped of that formatting (depending on my settings) and I'd expect the second line to be left alone. But it appears that your plugin can't determine the difference between actual HTML that affects rendering and HTML typed into a text editor and then copied over.
Hi there.
I am trying to use the paste mode option, and I found something buggy.
This part of code is wrong:
if (allowedTags.length > 0) {
allowedTags = (((allowedTags||'') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
out = out.replace(tags, function($0, $1) {
return allowedTags.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''
});
}
You are converting to a string:
allowedTags = (((allowedTags||'') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
Then you use indexOf. The problem is that no matter which config we enter, indexOf will always return -1, and every node will be removed.
Also, this is wrong:indexOf('<' + $1.toLowerCase() + '>')
Code which is working for me:
if (allowedTags.length > 0) {
//allowedTags = (((allowedTags || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
out = out.replace(tags, function ($0, $1) {
return allowedTags.indexOf($1.toLowerCase()) > -1 ? $0 : ''
});
}
Any thought?
Regards.
Hello,
I tried your extension and It work well. However I have an issue with formatted text. Words uses span tags to format the text. As an example I wrote a text in red and when I use your extension the text is pasted but the format (font, color, fontsize,...) is not applied. The text appear black with default format and fontsize
I looked at the code and it seems to be due to the fact that your remove span tags. Why do span tags are removed automatically ? and not treated as the other tags in the badTag array ?
Thanks,
Legacy html content often contains blockquote
tags for improper indentation of text outlines. Any chance you could add a filter to convert them into the proper indentation used by summernote? Since summernote sets margin-left
explicitly with an inline style, it could be tricky to keep track of the nesting.
A simpler alternative might be just to convert blockquote
tags to div
tags with a class of indent
. Then a CSS could be added like .indent { left-margin: 10px; }
and allow inheritance to take care of things.
This would be super helpful!
var cleanText = function (txt, nlO) {
var out = txt;
if (!options.cleaner.keepClasses) {
var sS = /(\n|\r| class=(")?Mso[a-zA-Z]+(")?)/g;
out = txt.replace(sS, ' ');
}
var nL = /(\n)+/g;
out = out.replace(nL, nlO);
by the time the code gets down to the
out = out.replace(nL, nlO);
all the '/n' characters have already been replaced in the line
var sS = /(\n|\r| class=(")?Mso[a-zA-Z]+(")?)/g;
out = txt.replace(sS, ' ');
Thanks for the plugin. The code has this:
tS = new RegExp('<' + bT[i] + '.*>.*<\/' + bT[i] + '>', 'gi');
Shouldn't this be:
tS = new RegExp('<' + bT[i] + '.*?>.*?<\/' + bT[i] + '>', 'gi');
I am unable to find a license for this plugin. Is it similar to Summernote i.e. MIT.
Please confirm. Thank you.
Hi, I noticed that in the example, after the badAttributes, is missing a final comma.
badAttributes: ['style', 'start'], // Remove attributes from remaining tags
limitChars: false, // 0/false|# 0/false disables option
Everything else works perfectly. Good job! ;)
copying a part of the text still paste the whole text instead and not just the copied part.
cleaner: {
notTime: 0, // Time to display Notifications.
action: 'paste', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
newline: '<br>', // Summernote's default is to use '<p><br></p>'
notStyle: 'position:absolute;top:0;left:0;right:0', // Position of Notification
icon: '<i class="note-icon">[Your Button]</i>',
keepHtml: true, // Remove all Html formats
keepOnlyTags: ['<p>', '<br>', '<ul>', '<li>', '<b>', '<strong>','<i>', '<a>'], // If keepHtml is true, remove all tags except these
keepClasses: false, // Remove Classes
badTags: ['style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html'], // Remove full tags with contents
badAttributes: ['style', 'start'] // Remove attributes from remaining tags
}
When I try paste from a word file it´s appears this console error:
Uncaught TypeError: Failed to execute 'setStart' on 'Range': parameter 1 is not of type 'Node'.
When im copying from word and i have a link on the text, the tag is erased too.
How i can keep this tag when im pasting on summernote?
Hello,
I encountered an issue by pasting text from a website
The html text was :
<HTML><HEAD><TITLE> LogFile </TITLE>
<SCRIPT id="LogScript" language="JavaScript1.2" src="/somepath"></SCRIPT>
<SCRIPT language="JavaScript1.2">
<!--
[Some script]
//-->
</SCRIPT>
<STYLE type="text/css">
/* reset */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
outline: 0;
font-size: 12px;
/* vertical-align: baseline; */
background: transparent;
}
</STYLE><!-- includes/footer.ihtml --> </HEAD><BODY onload="Loading();"><DIV id="Errors"><TABLE class="Error" id="Ob2197" border="2"><TBODY><TR class="Member" id="Mb$Program"><TD nowrap=""><TABLE class="Error" id="Ob2201" border="2"><TBODY><TR class="Member" id="Mb$Context"><TD class="Data" nowrap=""><!--StartFragment-->-ERR Logon failure: unknown user name or bad password.
<BR><!--EndFragment--></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></DIV></BODY></HTML>
And the regex (with "head" as badTag) didn't match correctly
It match Head with the "thead" inside the <style> tag. So that the match was :
<Head> [...] thead [...]</STYLE>
instead of <HEAD></HEAD>
I Hope the explanation are clear :)
I suggest replacing
tS=new RegExp('<'+bT[i]+'.*?'+bT[i]+'(.*?)>','gi');
with
tS=new RegExp('<'+bT[i]+'\\b.*>.*</'+bT[i]+'>','gi');
It permit to match the exact word and the closing tag
When I copy text from an MS Word document into the editor then it initially sorts the order in reverse. e.g. if my text contains a sentence followed by several bullet points then it pastes the bullet points in reverse order (the last one first) then followed by the sentence. (see image 1).
If I select the text in the editor, delete it and re-paste then it pastes it across correctly (see image 2)
The issue seems relates to Windows 10 Pro (64-bit) on a Microsoft Surface Pro 4 tablet on IE11, Firefox 51.0.1,, Chrome (56.0.2924.87).
It does, however, work correctly on Microsoft Edge (38.14393.0.0)
It also seems to work ok on Windows 10 on a PC and on Windows 7 on a PC.
Thanks
Lee
Could there be a reason why when pasting content it does not format the text correctly? It lumps all the text into 1 paragraph tag instead of separated paragraphs and lists.
When I set the action to just button, it works perfectly.
Bootstrap v4.1.2
jQuery v3.2.1
Summernote v0.8.9 (BS4 version)
If you select a fragment of text and press clean button, all tags will be removed.
happen because of code
if ($note.summernote('createRange').toString())
$note.summernote('pasteHTML', $note.summernote('createRange').toString());
it returns text without any tags
First off, great plugin for summernote! It's definitely exactly what I was looking for. The only thing I'm encountering is that it's cleaning all tags even with keepHTML: true
and any tags in keepOnlyTags
doesn't matter.
This is how I'm currently initializing it.
cleaner:{
action: 'both',
newline: '<p><br></p>', // Summernote's default is to use '<p><br></p>',
notStyle: 'position:absolute;top:0;left:0;right:0',
icon: '<i class="far fa-eraser"></i>',
keepHtml: true,
keepOnlyTags: ['<p>', '<b>', '<span>', '<br>', '<ul>', '<li>', '<strong>','<i>', '<a>'],
keepClasses: false,
badTags: ['style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html'],
badAttributes: ['style', 'start']
}
Any thoughts? thanks
Hi
Is it possible to initalize the cleaner for just a specific instance of summernote.
I am using IE11, summernote 0.8.2, bootstrap 3.3.7, jquery 2.1.3 and the latest version of the cleaner.
The issue that I have noticed is that by just including the reference to the cleaner plugin in the script on the page (without even initalizing the cleaner), it activates the cleaner on all of the summernote instances I have on the form.
Even if I include the cleaner initalize code on a specific instance of summer note, it enables the cleaner on all instances of summernote.
On my form I have multiple instances of summernote. However I want to use the cleaner on specific summernote instance only.
Can you please guide / help
Many thanks in advance
Regards
dairymilk7777
Hello,
I am trying to use this plugin but it does not appear to work. I load it after the summernote js file, I also added some default parameters by setting cleaner: {} when initializing summernote with my text area. But it does not appear to do anything, I can paste from Word and none of the badTags or badAttributes are removed. The console does not have any errors.
I am using Firefox 51.0.1 under Fedora Linux 25 and bootstrap 4 beta 6.
Thank you.
Hi,
The NPM package from here https://www.npmjs.com/package/summernote-cleaner is out of date by 4 years now.
When we follow the github link from npmjs it ends up to this repository even if the link itself is different on npmjs (https://github.com/StudioJunkyard/summernote-cleaner)
Hi there,
I selected some paras from the cleaner readme and ctrl-v pasted as my first test - and got an error right away.
paras were from "summernote-cleaner removes ...." to "... count in the status bar area."
browser is chrome 88.0.4324.182 on windows 10
i didn't set $.summernote.options.cleaner
summernote version 0.8.18
Line above seems to work, but not completely.
Means, the cleaned code is okay (after several tweaks from here, there and stackoverflow and somewhere else ..) > because the original code of the cleaner here removes the pasted text completely!, [with console.debug(..) output], the final inserted HTML looks then like this:
<p><br></p><p>Some text from MS-Word <br></p><p><br></p><p><b>in bold</b></p><p><br></p>
But is should be that:
Some text from MS-Word<br><b>in bold</b>
Seems that summernote itself is handling the emptyParam wrong!
There are a "few"
<p><br></p>
too much ..
Could this be fixed (while it is a summernote bug, affected is the cleaner function, that is why I am opening the issue here)?
thx!
Is it possible to configure different behavior for paste and button actions?
On paste necessary to clean only few tags, but on button pressing it should erase more tags
Different keepOnlyTags arrays for paste/button enough for me
The readme should show use braces instead of square braquets.
cleaner:{
element:'#description',
time:900,
action:'paste'
}
Thank you for this plugin !
Sorry to be so dense, but I just couldn't see where to do this.
In the readme (https://github.com/StudioJunkyard/summernote-cleaner#1-include-js) it says "Include the following code after you include Summernote." Where exactly is that? I have a Rails app that I've inherited, and I "include" Summernote in a few different places (Gemfile, application.js, stylesheets/base.scss). None of those places seem the right spot for the html code the readme presents. Any suggestions would be amazing!
I could not find license information about this project. It will be great if you can add license information to your project.
summernote-cleaner plugin causes an issues which is not reproducible if use summernote without the plugin. Ctrl+V pastes to beginning instead of at cursor, but Paste from Context Menu pastes okay.
Reproducible in IE, but not in Chrome.
This screencast demonstrates the issue.
Test Case
<!DOCTYPE html>
<html>
<head>
<title>IE paste issue</title>
<!-- include libraries(jQuery, bootstrap) -->
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel="stylesheet">
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.js"></script>
<!-- include summernote css/js-->
<link href="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.4/summernote.css" rel="stylesheet">
<script src="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.4/summernote.js"></script>
<script src="/summernote/plugin/summernote-cleaner.js"></script>
<script>
$(document).ready(function () {
$('#summernote').summernote({
height: 300,
cleaner: {
notTime: 0, // Time to display Notifications.
action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
newline: '<br>', // Summernote's default is to use '<p><br></p>'
keepHtml: false, //Remove all Html formats
keepClasses: false, //Remove Classes
badTags: ['style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html'], //Remove full tags with contents
badAttributes: ['style', 'start'] //Remove attributes from remaining tags
}
});
});
</script>
</head>
<body>
<div id="summernote">
<p>Begin</p>
<p> </p>
<p>End</p>
</div>
</body>
</html>
Hi,
I have this setup with summernote 0.8.3;
e.g.
$('.js-summernote').summernote({ toolbar: [ [ 'view', //['cleaner',['cleaner']], ['cleaner','picture', 'link', 'table', 'codeview'] ] ], lang: 'en-US', cleaner:{ notTime: 2400, // Time to display Notifications. action: 'button', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options. newline: '<br>', // Summernote's default is to use '<p><br></p>' notStyle: 'position:absolute;top:0;right:0;width:40%;', // Position of Notification icon: '<i class="glyphicon glyphicon-copy"></i>', keepHtml: false, // Remove all Html formats keepClasses: false, // Remove Classes badTags: ['style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html'], // Remove full tags with contents badAttributes: ['style', 'start'] // Remove attributes from remaining tags }, width: "100%", height: 180, // set editor height minHeight: 180, // set minimum height of editor maxHeight: null, // set maximum height of editor focus: false, // set focus to editable area after initializin callbacks: { onInit: function() { that = $(this); }, onImageUpload: function(files) { for(var i=0; i < files.length; i++) { console.log(files); summernoteSendFile(files[i], that); } } } });
What's happening is that on click on the button its always removing all content - any thoughts?
Thanks!
It would be great to make official releases, so we can catch up with new ones with a feed reeder.
This is a great little project - thank you for working on it!
Do you know of a way to remove the <p><br></p>
that summernote inserts before and after pasted text? Much is written about this issue, but I haven't been able to figure out a way around it.
I got the following config but it doesn't seem to work properly. If i paste some text that has bullets in it (<ul>
or<ol>
elements), it won't do it correctly, it pastes it as plain text, bullets are changed to dashes.
cleaner: {
notTime: 0,
action: 'paste',
newline: '<br>',
keepHtml: true,
keepOnlyTags: ['<p>', '<br>', '<ul>', '<li>', '<b>', '<strong>', '<i>', '<u>', '<s>', '<a>', '<ol>'],
keepClasses: false,
badTags: ['style', 'script', 'applet', 'embed', 'noframes', 'noscript', 'html', 'code', 'img'],
removeTagsButSaveContents: ['blockquote'],
badAttributes: ['style', 'start', 'class']
}
Any idea of what could be happening?
When using bootstrap 4 beta 3 and summernote 0.8.9, an error occurs:
Uncaught Error: TOOLTIP: Option "container" provided type "undefined" but expected type "(string|element|boolean)". at Object.typeCheckConfig (util.js:148) at l.A._getConfig (tooltip.js:643) at new l (tooltip.js:133) at HTMLButtonElement.<anonymous> (tooltip.js:704) at Function.each (jquery.min.js:2) at r.fn.init.each (jquery.min.js:2) at r.fn.init.l._jQueryInterface [as tooltip] (tooltip.js:695) at e.callback (summernote-bs4.min.js:3) at e.render (summernote-bs4.min.js:3) at summernote-cleaner.js:100
When using _bootstrap 4 beta 2 - no error
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.