nette / schema Goto Github PK
View Code? Open in Web Editor NEW📐 Validating data structures against a given Schema.
Home Page: https://doc.nette.org/schema
License: Other
📐 Validating data structures against a given Schema.
Home Page: https://doc.nette.org/schema
License: Other
Version:
v1.2.3
When expecting a string with a default value set, the default value is only returned when key does not exist in data array. I would expect it would return default value "string()" when I have specified null in property "test" in data array. The property "test" is not nullable on purpose, because default value should be returned instead.
$config = new Configuration([
'test' => Expect::string('hey')
]);
$config->merge([
'test' => null,
]);
$value = $config->get('test');
The exception I get is
Uncaught Nette\Schema\ValidationException: The item 'test' expects to be string, null given.
I would expect the variable $value
would be equal to "hey".
I think the problem is in the loop at master:src/Schema/Elements/Structure.php:175.
Class \Nette\Schema\Context
is marked as @internal
.
Lines 15 to 18 in febf71f
It causes that I cannot make custom validator object because the ->normalize()
method requires Context
instance in argument declared by \Nette\Schema\Schema
interface.
PhpStorm is here firing inspection warning:
Class 'Context' is marked as @internal
Inspection info: Reports the usages of the entities that are marked as @internal.
In most cases, such usages should be removed or replaced with other constructs.
In addition Context
is not fully internal, because custom validators need it for for collecting validation errors. I didn't found possibility to do it with more clean way.
Example of use:
https://github.com/redbitcz/subreg-api-php/blob/60f377ac68f3c1871b926eca336f5eb8d3368455/src/Schema/DateTime.php#L44-L64
Unmark \Nette\Schema\Context
as internal.
I understand the Context
class has currently too open public interface which is potentially sensitive. Maybe move collecting errors to more specifics object which will not be @internal
marked.
Hi,
I encounter a bug with the merge function when the schema use a list of string.
Here is an example :
`
$schema = Expect::structure([
'paths' => Expect::arrayOf('string')->default(['path_1']),
]);
$data = ['paths' => ['path_1']];
`
after the processSchema() i got the following result :
['path_1', 'path_1']
but i expect the values to be merged, and only have ['path_1']
as a result. This wrong behavior is when the array has numeric keys. If a use an associative array (ex : ['default' => 'path_1']) the merge work fine and i got only on element for the result.
Keep up the good work, this library is amazing :)
I know a tuple isn't a PHP structure, but you can still format and validate your data like it is. For instance, I want to validate a list of tuples:
[
[date, date, [ints], int],
[date, date, [ints], int],
[date, date, [ints], int],
// etc, 20 or 100 more lines, so I don't want assoc arrays (Expect::structure())
]
The outside would be a listOf()
, but a list of what? Tuples. Numeric arrays with a set amount of items that have specific types: elements 0 and 1 are dates, element 2 is a listOf(int())
, element 3 is an int()
.
Maybe something like
$schema = Expect::listOf(Expect::tuple(
Custom::date(),
Custom::date(),
Expect::listOf(Expect::int()),
Expect::int(),
));
Version: 1.0.2
String expectation with min(0)
and a nullable flag does not accept null.
Expectation is defined as
$schema = Expect::structure([
'position' => Expect::string()->min(0)->nullable(),
]);
When validating with ['position' => null]
, the error message raised is
"The option 'position' expects to be string or null in range 0.., null given."
The null
value should be accepted according to the error message.
If failing for other reasons, null
should be mentioned first in the error message for it to make more sense (only strings have "ranges")
Nullable does not take precedence over string length constraint even when minimal string length is 0
Version: latest
Got a warning:
Undefined array key
0
plus error:
Call to a member function
toString()
onnull
in
schema/src/Schema/ValidationException.php
Line 29 in b345035
Sorry, I don’t know what caused that.
The error messages created by the library are quite nice (they contain path, expected type and actual values). However in usability in real world APIs (at least at our company) we need programatically to map these error messages to either our error codes (numbers or strings) or custom error messages for example to provide more details, links or just keep consistent with our documentation language.
When you have more asserts for one value it is difficult to find which assert failed.
Same issue if you have more complex logic in your assert. For example you asserts relations between two fields of structure. In this case you get error like this
Failed assertion #0 for option 'data › items › 0' with value object.
which is not so useful.
What do you think about this feature. May I prepare PR?
What about some kind of Definition generation direct from Schema? It can be useful for implement some business logic depend on defined schema. Maybe something like this:
Basic type:
$schema = Expect::string()->required()->nullable();
$definition = $schema->getDefinition();
$definition->getType();
$definition->isRequired();
$definition->isNullable();
Array:
$schema = Expect::structure([
'name' => Expect::string()->required(),
'surname' => Expect::string()
]);
$definition = $schema->getDefinition();
foreach ($definition->getItems() as $item) {
$item->isRequired();
...
}
Example usage:
// Some kind of input processing in presenter
$input = (new UberUserInput('id', $_GET))->setSchema(Expect::integer()->required());
try {
$input->validate();
} catch (...) { ... }
// Generating form for user input
$form = new Form();
$form->addText($input->getName());
if ($input->getSchemaDefinition()->isRequired()) {
$form[$input->getName()]->setRequired();
}
I am not sure, if I am using this right, but I am trying to convert input string to result DateTime object
https://fiddle.nette.org/nette/#4f2cd78abf
class Foo {
public \DateTime $bar;
}
$processor = new \Nette\Schema\Processor;
$processor->process(
\Nette\Schema\Expect::structure([
'bar' => \Nette\Schema\Expect::string()->castTo('DateTime')
])->castTo(Foo::class),
[
'bar' => '2021-01-01',
]
);
Will result in the error:
Nette\Utils\Arrays::toObject(): Argument #1 ($array) must be of type iterable, string given, called in ...../vendor/nette/schema/src/Schema/Elements/Base.php on line 174
Input string will be successfully mapped to target class DateTime property.
I would like the ability to mark certain options as being 'deprecated'. These are elements that are still currently allowed but may be removed in future versions. Using a deprecated option should cause a silenced E_USER_DEPRECATED
error to triggered when used.
Add a new method called deprecated()
to the Base
trait - something like this:
/**
* Marks this option as deprecated
*
* @param ?string $message Optional deprecation message (any '%s' in the string will be replaced with the option path)
*
* @return $this
*/
public function deprecated(?string $message = null): self
{
$this->deprecated = $message ?? "Option '%s' is deprecated";
return $this;
}
Users can then flag options as deprecated like so:
$schema = Expect::structure([
- 'foo' => Expect::string(),
+ 'foo' => Expect::string()->deprecated('"%s" was deprecated in v2.1 and will be removed in v3.0; use "bar" instead'),
+ 'bar' => Expect::string(),
]);
At some point during the complete()
method call we'd raise a silenced deprecation error if any value was provided:
// TODO: Replace $valueWasProvided with the actual logic needed
if ($valueWasProvided && $this->deprecated !== null) {
$s = implode("', '", array_map(function ($key) use ($context) {
return implode(' › ', array_merge($context->path, [$key]));
}, $hint ? [$extraKeys[0]] : $extraKeys));
@trigger_error(sprintf($this->deprecated, $s), E_USER_DEPRECATED);
}
The @trigger_error('...', E_USER_DEPRECATED)
pattern is borrowed from Symfony and other projects:
Without the @-silencing operator, users would need to opt-out from deprecation notices. Silencing swaps this behavior and allows users to opt-in when they are ready to cope with them (by adding a custom error handler like the one used by the Web Debug Toolbar or by the PHPUnit bridge).
See https://symfony.com/doc/4.4/contributing/code/conventions.html#deprecating-code for more details.
Context
too if that's desired?Yes - but I could use a little guidance on the best place to put the deprecation check and how to properly determine if a value is given or omitted.
Version: 1.0.0
$schema = Expect::listOf('string')->default([
'foo',
'bar',
]);
$processor = new Processor();
$array = $processor->process($schema, [
'foo',
'bar'
]);
var_dump($array);
dumps:
array(4) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
[2]=>
string(3) "foo"
[3]=>
string(3) "bar"
}
$schema = Expect::arrayOf('string')->default([
'foo',
'bar',
]);
$processor = new Processor();
$array = $processor->process($schema, [
'foo',
'bar'
]);
var_dump($array);
dumps:
array(4) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
[2]=>
string(3) "foo"
[3]=>
string(3) "bar"
}
Is it the right behavior?
It will useful to have a possibility to convert integration schemas (XSD, JsonSchema, WADL, ...) to Nette/Schema or back.
Because of that, it will be nice to have a possibility to setup, not just Expectations, but also description for the Structure or it parts
I'm trying to solve recursive schema validation in menu library when you have tree structure of menu (submenu items).
Here's what I did as temporary solution:
final class MenuExtension extends CompilerExtension
{
public function getConfigSchema(): Schema
{
return Expect::arrayOf(Expect::structure([
'loader' => Expect::string(DefaultMenuLoader::class),
'items' => Expect::array()->required(),
]));
}
public function getItemSchema(): Schema
{
return Expect::structure([
'title' => Expect::string(),
'link' => Expect::string(),
'items' => Expect::array(),
]);
}
public function loadConfiguration(): void
{
$config = $this->getConfig();
$builder = $this->getContainerBuilder();
$processor = new Processor;
foreach ($config as $menuName => $menu) {
$container->addSetup('addMenu', [
$this->loadMenuConfiguration($builder, $processor, $menuName, $menu),
]);
}
}
private function loadMenuConfiguration(
ContainerBuilder $builder,
Processor $processor,
string $menuName,
stdClass $config
): ServiceDefinition {
$loader = $config->loader;
if (!Strings::startsWith($config->loader, '@')) {
$loader = $builder->addDefinition($this->prefix('menu.'. $menuName. '.loader'))
->setType($config->loader)
->setAutowired(false);
}
if ($loader->getType() === DefaultMenuLoader::class) {
$loader->setArguments([$this->normalizeMenuItems($processor, $config->items)]);
}
}
private function normalizeMenuItems(Processor $processor, array $items): array
{
array_walk($items, function(array &$item, string $key) use ($processor): void {
$item = $processor->process($this->getItemSchema(), $item);
if ($item->title === null) {
$item->title = $key;
}
$item->items = $this->normalizeMenuItems($processor, $item->items);
});
return $items;
}
}
Full file is here
Expect::schemaFactory([$this, 'getItemSchema'])
so function getItemSchema()
could be called recursively as long as submenu's are present.Processor
in CompilerExtension
so I don't need to create one when I have custom config.I believe it is not so uncommon to use such library when validating API parameters which is inherently connected to reporting errors back to the caller.
In such situation it is not uncommon to provide path* within the request payload and nette/schema does that in way that is not directly usable to be passed back to the response as it is available only (as far as I investigated) to the exception message.
So it would be nice to be able to access the path programmatically.
The Expect::from
is nice, but it does not really work for immutable objects like the following:
<?php
use Money\Money;
final class InvoiceItem {
public function __construct(
public readonly string $name,
public readonly Money $price,
public readonly int $amount,
) {
}
}
<?php
use Money\Money;
final class InvoiceItem {
public function __construct(
private string $name,
private Money $price,
private int $amount,
) {
}
public function getName(): string {
return $this->name;
}
public function getPrice(): Money {
return $this->price;
}
public function getAmount(): int {
return $this->amount;
}
}
It would be nice to have Expect::fromClass(InvoiceItem::class)
, that would check the constructor arguments instead of the public properties like Expect::from
does.
I can try to implement this, if you think this is a good idea.
Version: 1.2.5
PHP 8.3.2
produces below warnings
Warning: Private methods cannot be final as they are never overridden by other classes in /var/www/vendor/nette/schema/src/Schema/Helpers.php on line 19
Warning: Private methods cannot be final as they are never overridden by other classes in /var/www/vendor/nette/utils/src/Utils/Reflection.php on line 18
Warning: Private methods cannot be final as they are never overridden by other classes in /var/www/vendor/nette/utils/src/Utils/Validators.php on line 18
this is casued by @internal annotation in Schema/Helpers.php
Use the package with PHP 8.3.2
No warnings
remove @internal annotation
It would like to be able to cast a value using a callable.
class Foo {
public static function fromString($bar) {
// some logic
}
}
$schema = Expect::structure([
'foo' => Expect::string()->castTo(fn ($v) => Foo::fromString($v));
'foo2' => Expect::string()->castTo('Foo::fromString');
});
I'm willing to make a PR for this if you accept the feature to be integrated in the project.
Version: 1.2.4
When calling castTo
twice, it produces a warning.
https://fiddle.nette.org/nette/#d9bfa4be37
No warning. Input string will be successfully mapped to target class DateTime property.
🤷
Version: 1.0.2
'limit' => Expect::int(null)->min(1)->nullable(),
When limit is null, exception throwed "The option 'limit' expects to be int or null in range 1.., null given."
Dump of variable $expected:
int|null:1..
expected:
int:1..|null
then everything is fine
Let's say I validate the data structure of a blog post. This blog post has a body and an author. And as we support anonymous comments, the user is NOT mandatory. The author is a structure as well and if it IS PROVIDED it MUST have an e-mail.
I have a schema like this:
- comment: structure
- body: string (required)
- author: structure (not required)
- email: string (required)
- name: string (not required)
At the moment I am unable to validate the data with Nette/Schema like that because if I mark the email as required I still get a validation exception even if the author (not required) is not provided at all.
My validation schema code is like:
Expect::structure([
'body' => Expect::string()->required(),
'author' => Expect::structure([
'email' => Expect::string()->required(),
'name' => Expect::string(),
]),
]);
And for data like this:
- body: Hello World!
I get The mandatory option 'author > email' is missing.
. I get the same result even if I explicitly set the structure as not required like this: 'author' => Expect::structure([ ... ])->setRequired(false)
.
This example is obviously all made up to make things clear but I've hit this issue three times in different places. One of them being writing MongoDB extension for DI and trying to validate the driverOptions (see the context, structure inside structure, but context not mandatory)
Is this a bug? Is it a feature? Or am I missing something?
We are obviously able to work around this by explicitly checking the values in all sorts of places but it is a shame we have to pollute our codebase, especially when we streamlined it pretty much by implementing Nette\Schema in the first place :-)
Version: v1.2.5
Since the v1.2.5 my code throws type error, because Helpers::getCastStrategy expects array or single value, but gets stdClass when using data object with constructor.
testArrayOfUsers
TypeError: RBCB\Schema\User::__construct(): Argument #1 ($xxxx) must be of type ?string, stdClass given, called in
vendor/nette/schema/src/Schema/Helpers.php on line 186
$json = Json::decode($data);
foreach ($json->data as $user) {
$schema = Expect::from(new User());
$user = $processor->process($schema, $user);
yield $user;
}
And the DTO
class User
{
public function __construct(
public ?string $yyyy = null,
public ?string $xxxx = null,
public ?string $zzz= null,
....
}
Im unsure if I need to cast it manually, so let me know if I use the schema correctly or not.
Editing the method_exists($type, '__construct')
for supporting stdClass.
Intention
My intention is to use the Schema both to normalize the data and to obtain default values. In the second case, there is no need to input any data, just obtain an array of default values, if there are any.
My use case
I have several Personas, each of which can have very different parameters (saved as JSON in database).
The structure of these parameters is defined by the Persona's Schema.
I use the Persona's Schema to normalize these parameters entered by user's input.
To create a new Persona, however, I need to generate default values for its parameters from its Schema, so that I do not have to define them separately, because – why not use the existing Schema for this?
Example of my Schema for one of the Persona's:
Expect::structure([
'nickname' => Expect::string()->default('Maniac'),
'skills' => Expect::anyOf('low', 'medium', 'high')->default('low')->required(true)
]);
I need to use this Schema for normalization, which works great, but also to get the default values that are defined there. I need to get this result from this Schema:
[
'nickname' => 'Maniac',
'skills' => 'low'
]
When creating a new Persona, I encode this into JSON and save into database as default parameters of the Persona.
My idea is to have something like:
// normalized data
$normalized = $processor->process($schema, $data);
// default values
$defaults = $processor->getDefaults($schema);
// or
$defaults = $processor->process($schema); // that is, no input data are specified, so it returns default values instead of normalized data
Hi,
It seems the controles like min/max or assert() are not appplyed on the default value.
This could be problematic if the default value is dynamic.
In my use case i should controle the application key is a hexa 64 chars, and by default we use the environement variable APP_KEY.
I also tryed to move the min/max/assert function after the chained ->default(), but the result is the same :(
return Expect::structure([ 'key' => Expect::xdigit()->min(64)->max(64)->default(env('APP_KEY')), ]);
Is that a bug or an "expected" behaviour ?
Keep up the good work.
Hi,
i tried to start an early php 8.4 adoption but since i do not know where the tail will finally end, i stopped it.
The problem is that several nette packages are bound to a max php version.
To make changes independently i would suggest to add "experimental" support to the nette packages, this would it make easier to fix upcoming issues with php 7.4 or further php versions.
If something like in: https://github.com/ItsReddi/schema/pull/1/files (tests.yaml) would be available in all nette packages it is maybe easier for the community to fix upcoming issues.
Version: 1.2.1
Since you're not using a standard LICENSE.md format it makes things like Enlightn fail and say my project is using packages that I'm not legally allowed to use. Fix your license.
laravel new app --jet
(With --jet
since it's Laravel Jetstream requiring this package in my case)composer require enlightn/enlightn
php artisan englightn
Enlightn will fail saying your package is illegal to use.Use of a standard LICENSE.md format that doesn't make security checks fail.
Use a standard LICENSE.md format...
Version: master
In readme.md
is this sentence Documentation can be found on the website.
. "website" links to https://doc.nette.org/schema
. But this page returns 404.
We have an API that maps the URL to PHP method and the POST JSON data to method arguments, for example curl -XPOST https://mysite/api/device/add -d'{"id": 1, "data": [1,2,3]}'
would call
class Device
{
/** @param array<int> $data */
function add(int $id, array $data)
}
Thanks to PHP strict typing, it checks the type of basic types (int, string, booo) but it does not check content of arrays. We are already using PHPStan to pass static analysis tests and it would be cool to enforce the already existing rules via Nette\Schema.
Using reflection, I can get the phpdoc but there is no way (I think) that I could validate it using Nette/Schema. To accomplish that, a new method fromPhpstanArrayShape(string $shape)
(with a much better name) would have to be created.
// this is just a sample with simple array shape but we sometimes use more coplicated ones as well
$schema = Expect::fromPhpstanArrayShape('array<int>'); // = Expect::arrayOf('int')
$data = [1, 'one', 2];
// this would throw an exception since the second element is not integer
$normalized = $processor->process($schema, $data);
Do you think this is something that could be useful?
It would be handy to provide expectations for datetime too, typically validation & parsing in given format. Apart from format typically we use min and max validations as well as cross validations for intervals between two or more provided values.
Hi,
time to time I would like to validate callback signature and/or its return value. In some scenarios you setup hook and such hook invocation may throw TypeError
lately because of incompatible signature. Another case is that you have to check type of returned value from callback in runtime. Both of such cases could be validated in setup time for example by:
Expect::signature(fn(int $level): string => '');
Such "signature validation" can by done by PHP typesystem itself by interface:
interface LogLevelTranslator
{
function __invoke(int $level): string;
}
and it is probably cleaner way but too verbose.
Would you consider to merge such functionality? Or maybe into Nette\Utils\Validators
?
It would be useful if required()
method had fingerprint like this:
public function required(bool $required = true): self
Imagine you want to generate the schema programmatically either to require all options or to allow for only partial result. Imagine some create operation versus update operation where either I have to provide all fields or only ones I want to edit.
public static function getSchema(bool $partialAllowed) {
return Expect::structure(
[
'name' => Expect::string()->required(!$partialAllowed),
]
);
}
I am implementing restfull api, particularly post, put and patch methods and this would hugely declutter my code.
I will send PR shortly.
Version: v1.0.2
before()
helper is not called on things nested in Expect::anyOf
.
test(function() { // normalization through anyOf
$schema = Expect::anyOf(Expect::string()->before(function($v) {
return (string) $v;
}));
Assert::same('1', $schema->normalize(1, new Context));
});
before()
should work consistently inside anyOf
.
I miss a description of what is dynamic() good for
Hi,
Nice piece of code. Do you think there is a way to access the $items property in the Structure::class ?
I use this value in 2 cases :
For the moment i do a reflection and i access the private property, but a public function getItems() could be usefull. Or perhaps an "iterator" inside the structure class.
What do you think about this feature, this could add more flexibility using this great library.
Keep up the good work.
Version: 1.0.0
We encountered a strange behavior when using normalization with Expect::structure()
. It seemed that when specifying a before()
method on a property inside the structure and passing an object to the Nette\Schema\Processor::process()
method, it would not get normalized.
I managed to track it down to the Nette\Schema\Elements\Structure::normalize()
method, specifically the is_array
method in the condition, which was indeed the case:
public function normalize($value, Context $context)
{
$value = $this->doNormalize($value, $context);
if (is_array($value)) {
foreach ($value as $key => $val) {
$itemSchema = $this->items[$key] ?? $this->otherItems;
if ($itemSchema) {
$context->path[] = $key;
$value[$key] = $itemSchema->normalize($val, $context);
array_pop($context->path);
}
}
}
return $value;
}
This code snippet reproduces the problem:
use Nette\Schema\Expect;
use Nette\Schema\Processor;
use Nette\Utils\ArrayHash;
// The example from https://doc.nette.org/en/3.0/schema#toc-custom-normalization, just wrapped it in a structure
$schema = Expect::structure([
'data' => Expect::arrayOf('string')->before(function ($v) { return explode(' ', $v); })
]);
$values = ['data' => 'a b c'];
$processor = new Processor();
// Simple array
// stdClass { data => [ 'a', 'b', 'c' ] }
$arrayResult = $processor->process($schema, $values);
// Non-iterable class
// Nette\Schema\ValidationException: The option 'data' expects to be array, string 'a b c' given
$objectResult = $processor->process($schema, (object) $values);
// An ArrayHash, or any class that implements \Traversable
// Nette\Schema\ValidationException: The option 'data' expects to be array, string 'a b c' given
$traversableResult = $processor->process($schema, ArrayHash::from($values));
If the structure is an object, it's properties get properly normalized.
I tried my best with rewriting the Nette\Schema\Elements\Structure::normalize()
method to support objects, and I came up with this:
public function normalize($value, Context $context)
{
$value = $this->doNormalize($value, $context);
if (is_array($value) || is_object($value)) {
// When non-iterable object is received, iterate through its public properties
$properties = is_iterable($value) ? $value : get_object_vars($value);
foreach ($properties as $key => $val) {
$itemSchema = $this->items[$key] ?? $this->otherItems;
if ($itemSchema) {
$context->path[] = $key;
if (is_object($value)) {
$value->{$key} = $itemSchema->normalize($val, $context);
} else {
$value[$key] = $itemSchema->normalize($val, $context);
}
array_pop($context->path);
}
}
}
return $value;
}
With this modification, all of the examples I showed in Steps to Reproduce section work as expected:
use Nette\Schema\Expect;
use Nette\Schema\Processor;
use Nette\Utils\ArrayHash;
$schema = Expect::structure([
'data' => Expect::arrayOf('string')->before(function ($v) { return explode(' ', $v); })
]);
$values = ['data' => 'a b c'];
$processor = new Processor();
// Simple array
$arrayResult = $processor->process($schema, $values);
// Non-iterable class
$objectResult = $processor->process($schema, (object) $values);
// An ArrayHash, or any class that implements \Traversable
$traversableResult = $processor->process($schema, ArrayHash::from($values));
dump($arrayResult); // stdClass { data => [ 'a', 'b', 'c' ] }
dump($objectResult); // stdClass { data => [ 'a', 'b', 'c' ] }
dump($traversableResult); // stdClass { data => [ 'a', 'b', 'c' ] }
Edit: I created a PR in case the solution would be acceptable.
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.