tabulapdf / tabula-extractor Goto Github PK
View Code? Open in Web Editor NEWExtract tables from PDF files
License: MIT License
Extract tables from PDF files
License: MIT License
see PDFBox operator setLineWidth
Also, see what to do with PDFs that draw lines as thin rectangles
(as per discussion in #16)
Motivation:
It is currently impossible to detect spreadsheets on an area of a Page
We could replace those optionals area
parameters that are everywhere with something like
page.extract_area(....).spreadsheets
It will inherit every attribute from its container Page
, and will redefine the text_elements
and ruling_lines
accessors.
I've come across several PDFs that have cells partially hidden by the cell on the next column:
(rows 27-30 - document here )
This causes columns to be misdetected, because the text in column 2 overlaps the text in column 3.
Should we get rid of computer vision entirely in favor of operators? We'd be much lighter and could probably turn on table detection in the webapp by default.
Do you plan to implement this feature in the command line tool?
Here's a case that we might want to look into: https://www.dropbox.com/s/0i6ae5kgtcy0frb/s-013163.pdf
It's definitely a "spreadsheet", but the lines-of-text / ruling-lines ratio is way below/above the heuristic's defined threshold.
Merging characters into words needs to be improved (see failing test case test_forest_disclosure_report
)
See what pdf.js, Poppler and/or PDFBox are doing.
see: jazzido/tabula#121
https://www.dropbox.com/s/xhxdqkpl619p3a8/tabla_subsidios.pdf
Two issues with that file:
make TextElement#should_add_space?
accept an optional parameter TOLERANCE_THRESHOLD
Using the new 0.7.0 release installed via jruby -S gem install tabula-extractor
, I get the following error when I use the --pages all
flag:
NoMethodError: undefined method `each' for nil:NilClass
extract at /Users/pschwarz/.rvm/gems/jruby-1.7.9/gems/tabula-extractor-0.7.0-java/lib/tabula/extraction.rb:55
call at org/jruby/RubyProc.java:271
each at file:/Users/pschwarz/.rvm/rubies/jruby-1.7.9/lib/jruby.jar!/jruby/kernel/jruby/generator.rb:246
each at org/jruby/RubyEnumerator.java:274
each_with_index at org/jruby/RubyEnumerator.java:411
main at /Users/pschwarz/.rvm/gems/jruby-1.7.9/gems/tabula-extractor-0.7.0-java/bin/tabula:94
(root) at /Users/pschwarz/.rvm/gems/jruby-1.7.9/gems/tabula-extractor-0.7.0-java/bin/tabula:131
load at org/jruby/RubyKernel.java:1099
(root) at /Users/pschwarz/.rvm/gems/jruby-1.7.9/bin/tabula:1
eval at org/jruby/RubyKernel.java:1119
(root) at /Users/pschwarz/.rvm/gems/jruby-1.7.9/bin/jruby_executable_hooks:15
However, if I use specific_install and install from the master branch via jruby -S gem specific_install https://github.com/jazzido/tabula-extractor.git
, it works correctly. It seems like there is something wrong with the script produced for RubyGems. This one's beyond my current ruby level.
Thanks Guys!
To replicate, select an area of text such that the bottom bound of the selection only barely below the bottom of the bottom line of text (i.e. the bottom line of text is entirely contained within the selection, but only barely). Observe how the bottom line is not included (at least in some cases).
Two possibilities: imgAreaSelect is returning coordinates for the selection smaller than the selection appears or the Tabula code is too strict in requiring characters to be entirely within the given coordinates. Maybe we should include characters whose midpoint is within the coordinates?
Now that Tabula.extract_table
guesses the extraction mode, the character merging algorithm fails for that particular test case.
It works on 'normal' mode. Fails when operating locally for each detected cell, still not sure why.
@jeremybmerrill any idea?
it's gross, let's refactor it.
Hi, I'm wondering about the ability to use tabula in this fashion:
e.g. a way to run Tabula on some PAAS like Google App Engine/EC2 and then access it 100% through HTTP
https://www.dropbox.com/s/7t3vlfhf0hniu86/offensetable.pdf
Culprit appears to be the last row (check across_vertical_ruling
in Tabula.merge_words
)
Verticals lines 6 and 7 aren't being detected. Check for bugs in collapse_vertical_rulings
and/or pdf_line_extractor.rb
...in the line extractor
If we detect that a set of horizontal and vertical lines form a grid, we can construct the set of cells using Bentley-Ottmann.
See:
https://github.com/jazzido/tabula-extractor/tree/text-extractor-refactor/test
so there's a rather easily remedied error :)
Lookie here: https://www.dropbox.com/s/1th6b6cqg872vss/54013-1027504503-3.pdf
It condensates every edge case I've ever encountered ("columns behind columns" of death, etc). @jeremybmerrill any ideas on how to tackle this one?
I found a slow!
In Spreadsheet#fill_in_cells we call get_cell_text
on every single cell. Page#get_cell_text
just wraps a call to Page#get_text
-- which runs a select over all the text elements on the page.
Obviously this is O(n2) and it doesn't need to be. We (by which I mean, "I", but not right this minute) can write a method to use group_by to get all the cell text at once.
I think this'll bring significant performance gains: in a recent 90 sec script run, 28.59 seconds was spent on 17018 get_cell_text calls.
I've modified LSD
so it uses float
instead of double
(halves memory consumption).
If there's the right kind of ruling lines, use the SpreadsheetExtractor method; otherwise, use the "traditional" Tabula method of finding lines with no text in them on the page.
from #16:
I haven't profiled the spreadsheets stuff; if it's quick enough, we can just do all/most of that work, see if there are a
handful of Spreadsheet objects that were detected, and if so, use the SpreadsheetExtractor method.
Instead of just a single page.
https://www.dropbox.com/s/0i6ae5kgtcy0frb/s-013163.pdf
There seem to be invisible lines, or lines close together.
Hey @jeremybmerrill,
SpreadsheetExtractor
inherits from ObjectExtractor
, but it doesn't really add new functionality.
What about somehow moving this inside Page
?:
page.spreadsheets(options).each do |spreadsheet|
spreadsheet.cells.each do |cell|
cell.text_elements = page.get_cell_text(cell)
end
y.yield page, spreadsheet
end
end
Both are PDFStreamEngine
s, it would be nice to extract both texts and lines in a single pass.
jruby 1.7.4 (ruby-1.8.7p370) 2013-09-21 fffffff on Java HotSpot(TM) 64-Bit Server VM 1.7.0_21-b11 [linux-amd64]
When I run tabula:
$ tabula test.pdf
Output:
NoMethodError: undefined method `require_relative' for main:Object
(root) at /home/metroxylon/.rvm/gems/jruby-1.7.4/gems/tabula-extractor-0.6.6-java/bin/tabula:4
load at org/jruby/RubyKernel.java:1068
(root) at /home/metroxylon/.rvm/gems/jruby-1.7.4/bin/tabula:1
eval at org/jruby/RubyKernel.java:1088
(root) at /home/metroxylon/.rvm/gems/jruby-1.7.4/bin/jruby_executable_hooks:14
Result:
Improved operation scenario,"","","","",""
Volume servers in:,2007,2008,2009,2010,2011
Server closets,"1,505","1,580","1,643","1,673","1,689"
Server rooms,"1,512","1,586","1,646","1,677","1,693"
Localized data centers,"1,512","1,586","1,646","1,677","1,693"
Mid-tier data centers,"1,512","1,586","1,646","1,677","1,693"
Enterprise-class data centers,"1,512","1,586","1,646","1,677","1,693"
"",Best pra,tice scena,io,"",""
Volume servers in:,2007,2008,2009,2010,2011
Server closets,"1,456","1,439","1,386","1,296","1,326"
Server rooms,"1,465","1,472","1,427","1,334","1,371"
Localized data centers,"1,465","1,471","1,426","1,334","1,371"
Mid-tier data centers,"1,465","1,471","1,426","1,334","1,371"
Enterprise-class data centers,"1,465","1,471","1,426","1,334","1,371"
"",State-of-t,e-art scen,rio,"",""
Volume servers in:,2007,2008,2009,2010,2011
Server closets,"1,485","1,471","1,424","1,315","1,349"
Server rooms,"1,495","1,573","1,586","1,424","1,485"
Localized data centers,"1,495","1,572","1,585","1,424","1,485"
Mid-tier data centers,"1,495","1,572","1,585","1,424","1,485"
Enterprise-class data centers,"1,495","1,572","1,585","1,424","1,485"
spreadsheet algorithm outputs Spreadsheet objects, the original algorithm outputs Table objects.
We should write an "interface" (i.e. a Ruby module with shared methods, or just a definition of the methods that ought to be defined on both objects) to simplify the API, providing comparison methods, rows
, cols
, etc. whose output is identical for both.
Write some real tests for the behavior in bin/tabula
I made a list of commands that all worked properly with my recent changes in https://github.com/jazzido/tabula-extractor/blob/pre07/test/test_bin_tabula.sh, but that's obviously not a sustainable way to do testing.
I'm not sure what the canonical way to do testing of a binary is.
Out of the test data, the files that don't copy-paste from Preview to Excel cleanly are:
On the other hand, Tabula chokes on some PDFs that copy-paste just fine! For example, page 2 of this PDF from this website.
I cooked up an AppleScript to do bulk copy-pasting in these cases: https://github.com/opennorth/copy_paste_pdf
I'm not sure why Tabula has trouble with the linked PDF, but maybe it will be another useful test case.
...in case we already detected them somehow. Related to jazzido/tabula#116
extractor = Tabula::Extraction::ObjectExtractor.new(pdf_file_path, [3])
for a page that doesn't exist should raise a comprehensible error message (currently it raises some stupid Java Array error)
I'm using the tool on windows (actually calling it from a python script since I couldn't find any python modules that could do what this awesome tool can do) and every time I output a CSV, I get an extra blank line between each row.
It looks like maybe I am getting both CR and an LF after each row and excel (or notepad++) interpret these as asking for an extra line.
In python I have had the same thing happen when I open a file in 'w' mode instead of 'wb' mode (windows doesn't insert its own newlines after each row in binary write mode).
Its not critical (easy enough to remove blank rows), but thought I would point it out.
Calling make_table_with_vertical rulings overrides Tabula's column boundary algorithm and should brutely divide text at the location of the column boundaries.
Instead, when text overlaps the column boundary location, ALL of the text on both sides of the column boundary, ends up in the first cell e.g. ["Contents of Cell OneContents of Cell Two", ""] when it should be ["Contents of Cell One", "Contents of Cell Two"](Or in worse cases where the text literally overlaps, something like ["Contents of Cell On", "Ceontents of Cell Two"])
The problem is with the stuff that merges cells around line 282 of table_extractor. That section of code should be made aware of column locations so that it doesn't merge two detected cells if there's a vertical ruling separating them (or something like that).
I don't have time to work on this right now, but I might later. Mostly noting it here so I don't forget. :)
and split up lib/tabula/entities.rb into individual files for each class, because that one file is getting out of hand
In the new line recognizer I'm using java.awt.geom.*
classes. They're better than our own geometry classes, let's use them.
Feature request.
I'd love to see an command line option to get informations about rectangles found by TableGuesser. A "dry run mode" to see what portion of PDF tabula-extractor will be processing.
I get an errror "undefined method load_pdf for Tabula::TableGuesser:Module"
http://i.imgur.com/weQRtqj.png
When I "fix" this changing (bin/tabula line 64):
page_areas = opts[:guess] ? Tabula::TableGuesser::find_rects_on_page(Tabula::TableGuesser::load_pdf(filename), page_index) : [area]
with:
page_areas = opts[:guess] ? Tabula::TableGuesser::find_rects_on_page(filename, page_index) : [area]page_index) : [area]
I get an error "ArgumentError: wrong number of arguments calling 'find_lines_on_page'"
http://i.imgur.com/ygeCfto.png
I have:
jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2013-01-08 6586) (Java HotSpot(TM) 64-Bit Server VM 1.7.0_21) [amd64-java]
Tried to install:
$ jruby -S gem install tabula-extractor
Output:
JRuby limited openssl loaded. http://jruby.org/openssl
gem install jruby-openssl for full support.
System.java:-2:in `arraycopy': java.lang.ArrayIndexOutOfBoundsException
from DefaultResolver.java:111:in `makeTime'
from DefaultResolver.java:277:in `create'
from DefaultResolver.java:317:in `handleScalar'
from DefaultResolver.java:435:in `orgHandler'
from DefaultResolver.java:455:in `node_import'
from org/yecht/ruby/DefaultResolver$s_method_1_0$RUBYINVOKER$node_import.gen:65535:in `call'
from CachingCallSite.java:146:in `call'
from RubyLoadHandler.java:39:in `handle'
from Parser.java:300:in `addNode'
from DefaultYAMLParser.java:676:in `yyparse'
from Parser.java:290:in `yechtparse'
from Parser.java:284:in `parse'
from YParser.java:152:in `load'
from org/yecht/ruby/YParser$s_method_0_1$RUBYINVOKER$load.gen:65535:in `call'
from JavaMethod.java:630:in `call'
from DynamicMethod.java:186:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from CallOneArgNode.java:57:in `interpret'
from LocalAsgnNode.java:123:in `interpret'
from NewlineNode.java:104:in `interpret'
from InterpretedMethod.java:180:in `call'
from DefaultMethod.java:174:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from CallOneArgNode.java:57:in `interpret'
from LocalAsgnNode.java:123:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:180:in `call'
from DefaultMethod.java:174:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from CallOneArgNode.java:57:in `interpret'
from NewlineNode.java:104:in `interpret'
from RescueNode.java:199:in `executeBody'
from RescueNode.java:118:in `interpretWithJavaExceptions'
from RescueNode.java:110:in `interpret'
from InterpretedMethod.java:180:in `call'
from DefaultMethod.java:174:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from FCallOneArgNode.java:36:in `interpret'
from InstAsgnNode.java:95:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from EnsureNode.java:96:in `interpret'
from BeginNode.java:83:in `interpret'
from NewlineNode.java:104:in `interpret'
from WhenOneArgNode.java:36:in `whenSlowTest'
from WhenOneArgNode.java:46:in `when'
from CaseNode.java:133:in `interpret'
from NewlineNode.java:104:in `interpret'
from InterpretedBlock.java:373:in `evalBlockBody'
from InterpretedBlock.java:346:in `yield'
from InterpretedBlock.java:303:in `yield'
from Block.java:194:in `yield'
from YieldNode.java:112:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedBlock.java:373:in `evalBlockBody'
from InterpretedBlock.java:346:in `yield'
from InterpretedBlock.java:303:in `yield'
from Block.java:194:in `yield'
from RubyKernel.java:1251:in `loop_1_9'
from org/jruby/RubyKernel$s_method_0_0$RUBYFRAMEDINVOKER$loop_1_9.gen:65535:in `call'
from CachingCallSite.java:299:in `cacheAndCall'
from CachingCallSite.java:117:in `callBlock'
from CachingCallSite.java:132:in `callIter'
from FCallNoArgBlockNode.java:32:in `interpret'
from NewlineNode.java:104:in `interpret'
from InterpretedMethod.java:160:in `call'
from DefaultMethod.java:166:in `call'
from CachingCallSite.java:299:in `cacheAndCall'
from CachingCallSite.java:117:in `callBlock'
from CachingCallSite.java:122:in `call'
from CallNoArgBlockNode.java:64:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:242:in `call'
from DefaultMethod.java:198:in `call'
from CachingCallSite.java:339:in `cacheAndCall'
from CachingCallSite.java:197:in `callBlock'
from CachingCallSite.java:202:in `call'
from RubyClass.java:819:in `call'
from DynamicMethod.java:194:in `call'
from WrapperMethod.java:62:in `call'
from CachingCallSite.java:329:in `cacheAndCall'
from CachingCallSite.java:188:in `call'
from FCallTwoArgNode.java:38:in `interpret'
from LocalAsgnNode.java:123:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from EnsureNode.java:96:in `interpret'
from InterpretedMethod.java:242:in `call'
from DefaultMethod.java:198:in `call'
from CachingCallSite.java:339:in `cacheAndCall'
from CachingCallSite.java:197:in `callBlock'
from CachingCallSite.java:202:in `call'
from CallTwoArgBlockPassNode.java:62:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:283:in `call'
from DefaultMethod.java:214:in `call'
from CachingCallSite.java:359:in `cacheAndCall'
from CachingCallSite.java:237:in `callBlock'
from CachingCallSite.java:252:in `callIter'
from CallThreeArgBlockNode.java:64:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:262:in `call'
from DefaultMethod.java:206:in `call'
from CachingCallSite.java:349:in `cacheAndCall'
from CachingCallSite.java:228:in `call'
from FCallThreeArgNode.java:40:in `interpret'
from NewlineNode.java:104:in `interpret'
from InterpretedBlock.java:373:in `evalBlockBody'
from InterpretedBlock.java:346:in `yield'
from InterpretedBlock.java:303:in `yield'
from Block.java:194:in `yield'
from RubyIO.java:1106:in `open'
from RubyKernel.java:283:in `open'
from org/jruby/RubyKernel$s_method_0_2$RUBYFRAMEDINVOKER$open.gen:65535:in `call'
from DynamicMethod.java:198:in `call'
from CachingCallSite.java:339:in `cacheAndCall'
from CachingCallSite.java:197:in `callBlock'
from CachingCallSite.java:212:in `callIter'
from FCallTwoArgBlockNode.java:34:in `interpret'
from NewlineNode.java:104:in `interpret'
from IfNode.java:119:in `interpret'
from IfNode.java:119:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:221:in `call'
from DefaultMethod.java:190:in `call'
from CachingCallSite.java:329:in `cacheAndCall'
from CachingCallSite.java:188:in `call'
from CallTwoArgNode.java:59:in `interpret'
from InstAsgnNode.java:95:in `interpret'
from NewlineNode.java:104:in `interpret'
from RescueNode.java:199:in `executeBody'
from RescueNode.java:118:in `interpretWithJavaExceptions'
from RescueNode.java:110:in `interpret'
from BeginNode.java:83:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:242:in `call'
from DefaultMethod.java:198:in `call'
from CachingCallSite.java:339:in `cacheAndCall'
from CachingCallSite.java:197:in `callBlock'
from CachingCallSite.java:202:in `call'
from RubyClass.java:819:in `call'
from DynamicMethod.java:194:in `call'
from CachingCallSite.java:329:in `cacheAndCall'
from CachingCallSite.java:188:in `call'
from CallTwoArgNode.java:59:in `interpret'
from DAsgnNode.java:110:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedBlock.java:373:in `evalBlockBody'
from InterpretedBlock.java:346:in `yield'
from InterpretedBlock.java:303:in `yield'
from Block.java:194:in `yield'
from RubyArray.java:1630:in `eachCommon'
from RubyArray.java:1637:in `each'
from org/jruby/RubyArray$i_method_0_0$RUBYFRAMEDINVOKER$each.gen:65535:in `call'
from CachingCallSite.java:299:in `cacheAndCall'
from CachingCallSite.java:117:in `callBlock'
from CachingCallSite.java:122:in `call'
from CallNoArgBlockNode.java:64:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:221:in `call'
from DefaultMethod.java:190:in `call'
from CachingCallSite.java:329:in `cacheAndCall'
from CachingCallSite.java:188:in `call'
from CallTwoArgNode.java:59:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from RescueNode.java:199:in `executeBody'
from RescueNode.java:118:in `interpretWithJavaExceptions'
from RescueNode.java:110:in `interpret'
from BeginNode.java:83:in `interpret'
from NewlineNode.java:104:in `interpret'
from InterpretedBlock.java:373:in `evalBlockBody'
from InterpretedBlock.java:346:in `yield'
from InterpretedBlock.java:303:in `yield'
from Block.java:194:in `yield'
from RubyArray.java:1630:in `eachCommon'
from RubyArray.java:1637:in `each'
from org/jruby/RubyArray$i_method_0_0$RUBYFRAMEDINVOKER$each.gen:65535:in `call'
from CachingCallSite.java:299:in `cacheAndCall'
from CachingCallSite.java:117:in `callBlock'
from CachingCallSite.java:122:in `call'
from CallNoArgBlockNode.java:64:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:139:in `call'
from DefaultMethod.java:158:in `call'
from CachingCallSite.java:289:in `cacheAndCall'
from CachingCallSite.java:108:in `call'
from VCallNode.java:85:in `interpret'
from NewlineNode.java:104:in `interpret'
from IfNode.java:119:in `interpret'
from IfNode.java:119:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:180:in `call'
from DefaultMethod.java:174:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from CallSpecialArgNode.java:67:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from CaseNode.java:138:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:180:in `call'
from DefaultMethod.java:174:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from FCallOneArgNode.java:36:in `interpret'
from NewlineNode.java:104:in `interpret'
from RescueNode.java:199:in `executeBody'
from RescueNode.java:118:in `interpretWithJavaExceptions'
from RescueNode.java:110:in `interpret'
from InterpretedMethod.java:180:in `call'
from DefaultMethod.java:174:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from CallOneArgNode.java:57:in `interpret'
from NewlineNode.java:104:in `interpret'
from BlockNode.java:71:in `interpret'
from InterpretedMethod.java:180:in `call'
from DefaultMethod.java:174:in `call'
from CachingCallSite.java:309:in `cacheAndCall'
from CachingCallSite.java:148:in `call'
from gem:24:in `rescue_1$RUBY$__rescue___0'
from gem:23:in `__file__'
from gem:-1:in `load'
from Ruby.java:692:in `runScript'
from Ruby.java:575:in `runNormally'
from Ruby.java:418:in `runFromMain'
from Main.java:286:in `run'
from Main.java:128:in `run'
from Main.java:97:in `main'
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.