Git Product home page Git Product logo

abbyy-to-alto's Introduction

about

abbyy-to-alto is a tiny Java library to convert abbyy.xml to alto.xml. It supports abbyy v10 and alto v2/alto v4.1.

include with maven

<repositories>
  <repository>
    <id>MyCoRe HQ</id>
    <url>http://artifactory.mycore.de/mycore-releases</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
  <repository>
    <id>MyCoRe HQ Snapshots</id>
    <url>http://artifactory.mycore.de/mycore-snapshots</url>
    <releases>
      <enabled>false</enabled>
    </releases>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupId>org.mycore</groupId>
    <artifactId>abbyy-to-alto</artifactId>
    <version>0.1-SNAPSHOT</version>
  </dependency>
</dependencies>

use the cli

abbyy-to-alto contains a very simple command line interface

  • checkout the project with git
  • do a "mvn clean install" in the checked out directory
  • use the cli
    • java -jar target/abbyy-to-alto-0.1-SNAPSHOT.jar convert {source abbyy xml} {target directory}
    • java -jar target/abbyy-to-alto-0.1-SNAPSHOT.jar directory {source directory} {target directory}

abbyy-to-alto's People

Contributors

dependabot[bot] avatar jschwertfeger avatar mewel avatar mikegerber avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

abbyy-to-alto's Issues

Bug Report: Missing ID attribute for <ComposedBlock Type ="Table"> in AbbyyToAltoConverter

Missing ID attribute for in AbbyyToAltoConverter

ID attribute is a required attribute for all BlockType. This is specified at both alto-v2.0.xsd and alto-3-1.xsd.

Adding the following code at AbbyyToAltoConverter.java can solve the problem of generating invalid XML document:

Line 90: AtomicInteger tableElementCount = new AtomicInteger(0);
Line 120: tableBlock.setID("Table_" + tableElementCount.incrementAndGet());

NullPointerException at at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:540)

As this exception is thrown in an Java XML class I assume it's hard to fix but maybe it is caused in your library. When trying to convert this file, A NullPointerException is thrown.

Using OpenJDK-8 on Ubuntu 16.04

java.lang.NullPointerException: null
	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:540)
	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:562)
	at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$IDREFSIterator.next(Lister.java:442)
	at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$IDREFSIterator.next(Lister.java:419)
	at com.sun.xml.internal.bind.v2.runtime.reflect.ListTransducedAccessorImpl.print(ListTransducedAccessorImpl.java:100)
	at com.sun.xml.internal.bind.v2.runtime.reflect.ListTransducedAccessorImpl.print(ListTransducedAccessorImpl.java:42)
	at com.sun.xml.internal.bind.v2.runtime.property.AttributeProperty.serializeAttributes(AttributeProperty.java:86)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeAttributes(ClassBeanInfoImpl.java:368)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:674)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
	at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:143)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157)
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
	at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:143)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:578)
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:326)
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:479)
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:308)
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:236)
	at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:95)
	at org.mycore.xml.JAXBUtil.marshalAlto(JAXBUtil.java:68)
	at org.kitodo.mediaserver.core.conversion.ocr.AbbyyToAltoOcrConverter.convert(AbbyyToAltoOcrConverter.java:68)
	at org.kitodo.mediaserver.core.actions.AbbyyToAltoOcrConvertAction.lambda$perform$0(AbbyyToAltoOcrConvertAction.java:87)
	at java.lang.Iterable.forEach(Iterable.java:75)
	at org.kitodo.mediaserver.core.actions.AbbyyToAltoOcrConvertAction.perform(AbbyyToAltoOcrConvertAction.java:82)
	at org.kitodo.mediaserver.core.services.ActionService.performImmediately(ActionService.java:149)
	at org.kitodo.mediaserver.core.services.ActionService$$FastClassBySpringCGLIB$$6de31ad7.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:685)
	at org.kitodo.mediaserver.core.services.ActionService$$EnhancerBySpringCGLIB$$ef80a675.performImmediately(<generated>)
	at org.kitodo.mediaserver.importer.control.ImporterFlowControl.performActions(ImporterFlowControl.java:386)
	at org.kitodo.mediaserver.importer.control.ImporterFlowControl.importWorks(ImporterFlowControl.java:332)
	at org.kitodo.mediaserver.cli.commands.ImportCommand.importWorks(ImportCommand.java:126)
	at org.kitodo.mediaserver.cli.commands.ImportCommand.call(ImportCommand.java:168)
	at org.kitodo.mediaserver.cli.Terminal.run(Terminal.java:84)
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:790)
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
	at org.kitodo.mediaserver.cli.CliApplication.main(CliApplication.java:42)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:496)
	at java.lang.Thread.run(Thread.java:748)
Could not convert OCR file '/srv/kitodo/mediaserver/files/BV040105361/serlarch_BV040105361_xml/serlarch_bv040105361_0303.xml'.
java.lang.NullPointerException: null
	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:540) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:562) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$IDREFSIterator.next(Lister.java:442) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$IDREFSIterator.next(Lister.java:419) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.reflect.ListTransducedAccessorImpl.print(ListTransducedAccessorImpl.java:100) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.reflect.ListTransducedAccessorImpl.print(ListTransducedAccessorImpl.java:42) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.AttributeProperty.serializeAttributes(AttributeProperty.java:86) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeAttributes(ClassBeanInfoImpl.java:368) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:674) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:143) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:143) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:578) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:326) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:479) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:308) ~[na:1.8.0_191]
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:236) ~[na:1.8.0_191]
	at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:95) ~[na:1.8.0_191]
	at org.mycore.xml.JAXBUtil.marshalAlto(JAXBUtil.java:68) ~[abbyy-to-alto-8c784b5b6f03d94e5e9771940a8bec9015a9c210.jar:na]
	at org.kitodo.mediaserver.core.conversion.ocr.AbbyyToAltoOcrConverter.convert(AbbyyToAltoOcrConverter.java:68) ~[kitodo-mediaserver-core-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at org.kitodo.mediaserver.core.actions.AbbyyToAltoOcrConvertAction.lambda$perform$0(AbbyyToAltoOcrConvertAction.java:87) ~[kitodo-mediaserver-core-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_191]
	at org.kitodo.mediaserver.core.actions.AbbyyToAltoOcrConvertAction.perform(AbbyyToAltoOcrConvertAction.java:82) ~[kitodo-mediaserver-core-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at org.kitodo.mediaserver.core.services.ActionService.performImmediately(ActionService.java:149) ~[kitodo-mediaserver-core-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at org.kitodo.mediaserver.core.services.ActionService$$FastClassBySpringCGLIB$$6de31ad7.invoke(<generated>) ~[kitodo-mediaserver-core-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.4.RELEASE.jar:5.0.4.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:685) ~[spring-aop-5.0.4.RELEASE.jar:5.0.4.RELEASE]
	at org.kitodo.mediaserver.core.services.ActionService$$EnhancerBySpringCGLIB$$ef80a675.performImmediately(<generated>) ~[kitodo-mediaserver-core-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at org.kitodo.mediaserver.importer.control.ImporterFlowControl.performActions(ImporterFlowControl.java:386) ~[kitodo-mediaserver-importer-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at org.kitodo.mediaserver.importer.control.ImporterFlowControl.importWorks(ImporterFlowControl.java:332) ~[kitodo-mediaserver-importer-1.0-SNAPSHOT.jar:1.0-SNAPSHOT]
	at org.kitodo.mediaserver.cli.commands.ImportCommand.importWorks(ImportCommand.java:126) ~[classes/:na]
	at org.kitodo.mediaserver.cli.commands.ImportCommand.call(ImportCommand.java:168) ~[classes/:na]
	at org.kitodo.mediaserver.cli.Terminal.run(Terminal.java:84) ~[classes/:na]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:790) ~[spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774) ~[spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-2.0.0.RELEASE.jar:2.0.0.RELEASE]
	at org.kitodo.mediaserver.cli.CliApplication.main(CliApplication.java:42) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
	at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:496) ~[na:na]
	at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_191]

This is the code I used to convert the file:

        // Read ABBYY file
        Document abbyyDocument;
        try (InputStream inputStream = Files.newInputStream(sourceFile, StandardOpenOption.READ)) {
            abbyyDocument = JAXBUtil.unmarshalAbbyyDocument(inputStream);
        }

        // Convert to ALTO
        Alto alto;
        try {
            AbbyyToAltoConverter converter = new AbbyyToAltoConverter();
            converter.setEnableConfidence(false);
            alto = converter.convert(abbyyDocument);
        } catch (Exception ex) {
            throw new Exception("Could not convert OCR file '" + sourceFile + "'", ex);
        }

        // Write ALTO file
        if (!Files.exists(destFile.getParent())) {
            Files.createDirectories(destFile.getParent());
        }
        try (OutputStream outStream = Files.newOutputStream(destFile)) {
            JAXBUtil.marshalAlto(alto, outStream);
        }

Invalid block type Barcode

Currently this library doesn't support the block type "Barcode". This is a valid block type in Finereader format. This library throws an Exception for this type. Real support may be difficult I think but it would be good to not throw exceptions for unsupported types which are valid. Maybe you could write to log for this block type?

} else if (blockType.equals("Barcode")) {
  LOGGER.warn("Unsupported block type '" + blockType + "' at " + blockRect);
} else {
  throw new ConvertException("Invalid block type " + blockType + " at " + blockRect);
}

Processing tags

I'm currently evaluating using this software to convert some ABBYY XML files and noticed that no ALTO ocrProcessing(v2)/Processing(v4) tags are generated.

I'm working on a PR to add these!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.