Giter VIP home page Giter VIP logo

node-wpapi's People

Contributors

aaronjorbin avatar artoliukkonen avatar bmac avatar brad-decker avatar breadadams avatar bt avatar carldanley avatar computamike avatar dimadin avatar edygar avatar freepad avatar gambry avatar gerhardsletten avatar gitter-badger avatar jasonphillips avatar jerolan avatar kadamwhite avatar luisherranz avatar ntwb avatar ohar avatar panman avatar sdgluck avatar tbranyen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-wpapi's Issues

Superagent adds duplicative array syntax

superagent is transforming the (already-formatted) type[]=type1&type[]=type2 portion of the URL into type[][0]=type1&type[][1]=type2. This results in the following error:

http://[test site url]/wp-json/posts?filter%5Bcity%5D=cityname&type%5B%5D=event&type%5B%5D=evttype
{ [Error: cannot GET /wp-json/posts?filter%5Bcity%5D=cityname&type%5B%5D[0]=event&type%5B%5D[1]=evttype (403)]
  status: 403,
  method: 'GET',
  path: '/wp-json/posts?filter%5Bcity%5D=cityname&type%5B%5D[0]=event&type%5B%5D[1]=evttype' }

This can be avoided by removing the [] around the type property, since superagent evidently inserts the array syntax and indices itself; effectively, the resolution here will be to eliminate the prepareParameters method from inside the wp-request module, as this behavior of superagent obviates our need to write the syntax ourselves.

I speculated in #84 (whence this issue) that we should consider tossing Superagent in favor of a request library with unexpected behavior; upon reflection, Superagent is doing an OK job and we'd be making undue extra work were we to switch. This bug will be fixed in isolation.

Deprecate "/posts/path" route

Conversation at WCNYC indicates the /posts/path route is not long for this world; filtering on post name is preferred. i.e.,

/pages?filter[pagename]=some/path

will replace

/wp-json/pages/path/some/path

This should work today, so the .path handler on the page collection should just be re-purposed to set .filter({ pagename: pathArg })

Support Node.js 0.8

The tests bomb out in travis in 0.8. I need to try this in Node.js 0.8 to see if I can figure out why.

Unknown method posts

I have installed the basic-auth and json-api plugins on wordpress.

This is my app.js file

wp.posts().then(function( posts ) {
   console.log( posts );
})

which returns this error:
{ status: 'error', error: 'Uknown method \'posts\'.' }

But using Postman, I am getting the desired output of an array of posts.
So my question is which url is this npm module hitting ?

<api-path>/get_posts/ -> gives correct output

or

<api-path>/posts/ -> gives error.

Thanks in advance.

Create Vagrant VM and document testing steps

We should create (probably as a separate repository) a Vagrant box that can be provisioned with the WP-API plugin and some dummy content, so that we can provide easy testing and development steps for potential library users. It's better to say "want to try this out? Download and install this thing, then run this to see it work, then customize it to see what else it can do" than to leave people to their own devices; Node is still relatively unbroken ground for the WP audience, the more we can do to make this easy for them to get started, the more they'll build with it.

I'd made a previous effort in this direction with my wpapi-vagrant-varietal repo, but it's a bit heavy and not tailored to be a foundation for learning about the client lib. @carldanley said he was taking a stab at putting together something better!

Make core lib environment agnostic to support use in the browser

It seems like a great deal of the value of this module would be useful in the browser as well, it wouldn't be too difficult to delegate all node specific code out into it's own location so that it can be swapped out for browser equivalents.

Is this something you all are potentially interested in? I could do this work.

Setup a testing suite

I'd like to get some unit testing in place so we can change the API around as needed but still run tests against things to make sure we're functioning as intended.

Add method to bind new endpoint handlers

Per @rmccue, providers of the WP-API can expose new query endpoints and we should acocunt for those.

[6/30/14, 3:01:29 AM] Ryan McCue:
So e.g. for WooCommerce, implementing /products as a shorthand for /posts?type=products

The option we discussed was to add a .customEndpoint method, in the manner of .registerType (see #7); however, the other option would be to make a function on WP that could be used to define a new constructor and specify the types at instantiation. Worth discussing. cc @carldanley

Improve the way in which query URLs are constructed

At present we're concatenating a collection of instance properties like _id, _action, and _actionId into the paths for API requests. This seems brittle, as it both clutters the instance properties and can't really systematically enforce the order in which they're assembled.

I've been looking at @cowboy's route-matcher, and I think this may be a more robust way of treating with these paths. Instead of having the method responsible for generating the PostsRequest's URI hard-code 'posts' as the first level of the path, we could specify posts to have a route template like 'posts/', then swap that out with 'posts/:id/:action/:actionId' when a method like posts().id( n ) is called. This way we can maintain two properties, a ._routeTemplate string and a _path object, and pass that object into the routeMatcher template to construct the main URL.

Note that this has no bearing on how the query parameters used for filtering will be handled; it just proposes a more rigorous structure for constructing the Path part of the URLs.

Expose Pagination to client consumers

Many responses from the API will be paginated, either explicitly or by default. For example, if post collections return 10 items by default, and you have 100 items in your DB, both of these will come back with a header link with rel="next":

wp.posts()...
wp.posts().filter('posts_per_page', 2)...

The general approach I have taken with this library is to let the user specify a request, and return what they asked for. At the moment the way this manifests is that if you request wp.posts()..., you get the posts collection (that is, the response body) back in the request callback: the headers are not included. I favored this over returning the full object, because this felt like a very clunky thing to have to do every time:

wp.posts().then(function(resp) {
  return resp.body;
}).then(function(posts) {
  // do something with the posts collection
});

when in many cases it's only the response body that you care about. The one exception is paginated collections, as it is the response header that includes the pagination information (via the link header and two custom header properties).

I want to provide an intuitive way to expose collection paging to users, preferably without making it substantially more verbose to request a specific resource. I am not sure how to proceed.

Option 1: Return the raw request object

This is what is outlined above, where the request object is passed back in full and you can manually grab the body or check the link header as you need to. This feels like a bad solution because it makes the default usage (get a specific resource) more difficult by requiring a pipe through a then, and it doesn't give you any additional functionality (you need to manually parse headers to handle paging).

Option 2: Augment the response data with paging properties

The idea is to augment the response object with properties or methods relating to pagination in cases when the resulting data is determined to be paginated. There were various ways it could be implemented:

Example 1, just expose the links and pagination headers:

wp.posts().then(function(posts) {
  // How many posts did I get back?
  console.log( posts.length ); // normal array

  // posts also has a .paging property, on which is set custom
  // pagination-related information

  // How many posts, total...?
  console.log( posts.paging.total );
  // Next collection URL?
  console.log( posts.paging.next );
  // Previous?
  console.log( posts.paging.prev );
  // total number of pages?
  console.log( posts.paging.totalPages );
});

Example 2, give the returned collection methods that would function as fully-fledged WPRequest objects

wp.posts().then(function(posts) {
  // Make a request for the next page of posts
  posts.next().then(function(nextPagePosts) {
    // and so on
    posts.next().then(function(thirdPagePosts) {
      // and (halcyon and) on and on
    })
  });
});

Things I like about this approach:

  • Doesn't expose the headers unless you asked for them directly (with wp.posts.head()), which keeps the data returned from the service directly accessible to clients without transformation
  • Fits the existing paradigm we've been discussing in #5, where collections would get augmented with Lodash operators (like Backbone collections/LayoutManager child view collections)

That said, when I pitched this to a collaborator he described it as "yucky" and I couldn't think of any other examples of systems that work this way. I want to present it for review anyway, though, because it was my first thought.

Option 2A: Let users define custom data augmentation methods

This is an extension of one way the above could be implemented. If you imagine a prototype method on CollectionRequest called transform, which could be used to register a methods that would be run on the request response before being returned (which would replace the existing returnBody and returnHeaders methods), you could let users opt-in to things like lodash collection methods, pagination handling, or their own custom transformations:

wp.posts().transform(wp.transforms.paginate).then(function(posts) {
  // posts has some default pagination methods/props
});
wp.posts().transform(wp.transforms.collection).then(function(posts) {
  // Now it's got Underscore methods, and this would work:
  posts.pluck('titles')...
});
wp.posts().transform(myPluckTitles).then(function(posts) {
  // posts is now an array of titles
});
wp.posts().transform(myGetPagingLinks).then(function(links) {
  // now it's a bare object of links -- or whatever you want it to be
});
// Could also be exposed directly, through a custom chaining method
wp.posts().paginated().get(/* ... */);

Upshots:

  • Much more flexible
  • Provides a consistent API for handling pagination, and for other types of transformation like augmenting collections with lodash collection methods

Downsides:

  • Would probably require making some sort of wp.transforms or wp.utils namespace to hold the default transformation methods
  • More chaining methods is not/should not be the answer to everything
  • Introduces unintuitive behavior vs having a consistent default

Option 3: Return a custom object

This is in some ways an extension of 1, just with some of the things I noted addressed. Basically, all requests come back as an object with four properties:

  • headers: the raw, unprocessed HTTP headers
  • body: the raw, unprocessed response body
  • data: the data returned by the request (which is the body, but the important part is that data feels more intuitive and accurate vs what the information actually is: I shouldn't care that it's the body, I care that it's the data returned from the server.)
  • paging/links: custom properties designating previous/next collections, etc

The advantage of this over the first option is that this, to me, feels more intuitive than returning resp.body:

wp.posts().then(function(resp) {
  // collection:
  console.log( resp.data );
  // pagination URL:
  console.log( resp.links.next );
});

The disadvantage is that I'm right back where I started in terms of having to pipe responses through yet another then in order to boil them down to the actual response body, if that's what I wanted originally.


I am probably overlooking additional options, here. These are the actual pagination-related header properties exposed by the API endpoint, for an example collection of two posts:

link: '</wp-json/posts?filter%5Bposts_per_page%5D=2&page=1>; rel="prev",
    </wp-json/posts?filter%5Bposts_per_page%5D=2&page=3>; rel="next",
    <http://my.site.com/wp-json/posts/214>; rel="item"; title="Article Title",
    <http://my.site.com/wp-json/posts/211>; rel="item"; title="Article Title"',
'x-wp-total': '17',
'x-wp-totalpages': '9',

It's possible the WordPress API itself could be extended to provide more headers or more links such as a first or last page, as it's a work-in-progress, but for the time being I'm trying to figure out if there's any other approach than the ones outlined above to make answering these questions (and others like them) easy:

Questions a solution should answer

  1. Are there more pages in this collection?
  2. What page am I on?
  3. How do I get to the previous/next pages?

Any recommendations for good API clients that handle this sort of behavior are welcome, as is commentary or dissension around the above options I have outlined.

Auth fails?

Auth seems to fail, even when explicityly setting it in the call:

wp.users().auth("USER","PASS").me(); 

gives an empty object, while calling wp-json/users/me with basic auth with curl returns my user info.

Add convenience method .posts().tags for setting tags filters

See the Tag Parameter filter reference from WP_Query documentation:

The following query filter parameters should be accepted by the filter method, but it would be ideal to make a wrapper that will intelligently maintain an internal array of tags to apply to the query:

  • tag (string) - use tag slug.
  • tag_id (int) - use tag id.
  • tag__and (array) - use tag ids.
  • tag__in (array) - use tag ids.
  • tag__not_in (array) - use tag ids.
  • tag_slug__and (array) - use tag slugs.
  • tag_slug__in (array) - use tag slugs.

As an example of intended usage, these should all be valid syntax:

wp.posts().filter({ tag: 'current-events' })...
wp.posts().tag( 'current-events' )...
wp.posts().tag( 'current-events' ).tag( 'boston' )...
wp.posts().tag([ 'current-events', 'boston' ])...

HTTP requests should be triggerable by HTTP verb methods or by promise syntax

For maximum flexibility and convenience, both callback and promise-style syntax should be valid: This is the rationale for pulling in a Promise lib as per #2

// Callback-style
wp.posts().filter({ posts_per_page: 7 }).get(function(err, data) {
  if ( err ) {
    // handle error
  } else {
    // do something with data
  }
});

// Promise-style
wp.posts().filter({ posts_per_page: 7 }).then(function(data) {
  // do something with data
}, function(err) {
  // handle error
});

Additionally, if .then is called on a query chain, it assumes the GET method unless otherwise specified:

wp.posts().id(7).data(postObj).put().then(function(response) { /* ... */ });

Double callback when using multiple custom post types?

Hi!
If I do this
wp.posts().type(['cityreport','place']).get(function (err, data) {
I get a console error "double callback", and then a "syntax error". Also my error callback gets called.
The URL called does include the right data, but parsing it fails? It is valid JSON.
Or am I doing something wrong?

Document authentication

We have hypothetical support for basic authentication, but it hasn't been tested. We should validate that it works, and figure out what would be needed to support OAuth.

Don't mock 3rd-party request library in tests

It was suggested in code review that using nock or an alternative to mock the server responses would be a more robust approach to testing the library, vs the current approach (in which our tests depend on the specific API provided by superagent).

Add .meta handler for posts

The /posts endpoint provides two routes for interacting with post meta values:

  • /posts/<id>/meta
  • /posts/<id>/meta/<mid>

Both of these depend on authentication, so that should be taken care of (and documented, see #33) before we implement this.

OAuth support

Hi!

What's involved with making oauth work (either oauth1, for which there is a WP API plugin plugin, or OAuth 2 for which there is a standalone plugin) with this plugin instead of standard HTTP authentication?

Kevin N.

Implement lo-dash/underscore support for return values

I had the idea of extending the return values to support lo-dash or underscore so the developer could make use of sorting functions (among everything else these libraries support).

Though, after talking with @kadamwhite - I feel that we should make this as lightweight as possible and let the developer choose how they want to take action on the return results.

Still opening the Issue for discussion on this topic.

Add integration test to ensure URI's are requested as intended

(For background see #89)

Superagent automatically converts multiple occurrences of the same query parameter key into array syntax: param=val&param=val becomes param[0]=val&param[1]=val. Because this behavior was not intuitive (other request libraries use the provided URL exactly as-is), we should add a test to verify that this behavior still occurs in case it changes in future versions of superagent.

Library is now 0.3.0 RC1

@carldanley I'm considering punting .media() to the next release in order to get this online and announced this week; whether or not we make that call, this whole thing is ready for code review.

Support mixing into or processing response data?

It's been suggested that we mix in Underscore/Lodash functions to collection responses (#5), and it may be beneficial to provide a way to process response data objects into more structured models. (This library does not actually provide models: we should not presume how users want to structure those data models, but it might be convenient to give them the option to do so.)

This issue is for brainstorming an interface for providing these models. It may not be feasible, given the variety of data types that you can retrieve even from a single endpoint (e.g. /posts and its descendants yields Posts, Post Collections, Comments, Revisions, and Types).

Multiple categories?

Is there a way to fetch an article with multiple categories, such as article has category "foo" and "bar" ?

Support Custom Post Types

Before this is released, we need to have support for custom post types.

Alternative syntaxes under consideration:

Generic types handled by a single types endpoint handler:

var wp = new WP( options );

wp.types( 'cpt_event_listing' ).then(/* ... */);

Create custom endpoint handlers with a registerType utility method (see #7):

var wp = new WP( options );
wp.eventListings = wp.registerPostType( 'cpt_event_listing' );

wp.eventListings().get().then(/* ... */);

In either case, the type method would wrap .posts() but would set properties to specify a type query parameter to filter results.

@carldanley, I'm particularly interested in your opinion here since you're familiar with the WP internals

Flesh out TaxonomiesRequest

Presently the only route that is anything close to fully-formed is the PostsRequest endpoint handler. Once #30 has been resolved, we should add support for getting lists of taxonomies and their terms through the /taxonomies API endpoint.

Filters should DO something

Calling .filter currently has no effect on the generated URL. That's less than ideal.

TODO:

  • Build a whitelist of accepted filter values, so we can fail out faster if an invalid value is provided
  • Build a list of filter values that should get treated as arrays in the resulting API request URI
  • Build a helper to create a query string to append to the generated URIs

The latter could potentially be accomplished by having generateRequestUri call back to the inherited WPRequest's own generateRequestUri, which could in turn process the _filters object. I'm inclined to pursue this because it keeps 100% of the filtering logic within WPRequest, without requiring any special treatment on the part of the inheriting request modules.

Add code coverage reporting

@tbranyen has indicated that setting up Istanbul may be difficult with simplemocha; I'm willing to give it a try though, as code coverage reporting would be, to borrow a phrase, "rad."

Add convenience method .posts().category for setting category filters

See the Category filter reference from WP_Query documentation:

The following query filter parameters should be accepted by the filter method, but it would be ideal to make a wrapper that will intelligently maintain an internal array of category IDs:

  • cat (int) - use category id.
  • category_name (string) - use category slug (NOT name).
  • category__and (array) - use category id.
  • category__in (array) - use category id.
  • category__not_in (array) - use category id.

As an example of usage,

// filters posts by those in categories 1, 2 & 3:
wp.posts().category( 1 ).category([ 2, 3]).get();

// specify a category by slug:
wp.posts().category( 'news' );

You should have a better syntax for querying custom post types

Idea: our WP install has a custom post type (CPT) with name cpt_event_listing: we should be able to register a convenience method to instantiate post queries

var wp = new WP( options );

wp.eventListings = wp.registerPostType( 'cpt_event_listing' );

wp.eventListings().get().then(/* ... */);

Having registerPostType also mutate the parent wp object automatically was also considered; coop recommendation was to make the interface a little more declarative. Some alternatives to that syntax:

// Automatically make a method for any registered CPT name:
// Should be safe b/c of similarity b/w valid CPT names and valid JS identifiers
wp.registerType('cpt_event_listing');
wp.cpt_event_listing().then(/* ... */);

// Don't make a convenience method at all and just always pass a CPT
// identifier to a wp.types function:
wp.types( 'cpt_event_listing' )...
wp.types( 'other_cpt_name' ).get()....

Use "var" instead of "const" to permit opting-in to strict mode

From a code review session, @jugglinmike points out

"the const keyword for your modules prevents code from opting into Strict mode."

I agree with his assessment that the benefits of Strict outweigh the benefits of const; we should change all usages of const back to var and add strict-mode enforcement to the .jshintrc

filter() doesn't work

running this

wp.posts().filter( 'posts_per_page', 5 ).get(function( err, results ) {
        if ( err ) {
            // handle err
        }
            // do something with the returned posts
                locals.data.posts = results;
                console.log(results);
                next();
            });

the result are always all the published post, with any kind of filter even

wp.posts().filter({post_status: 'draft',category_name: 'news'}).get(function( err, results ) {
        if ( err ) {
            // handle err
        }
            // do something with the returned posts
                locals.data.posts = results;
                console.log(results);
                next();
            })

Am I doing something wrong?

Add .taxonomy('tax_name') shortcut for .taxonomies().taxonomy('tax_name')

The .taxonomies().taxonomy( 'taxonomy_name' ) pattern is a bit repetitive.

To avoid having to type "taxonomy" twice whenever querying for a custom taxonomy or custom taxonomy terms, we should add a .taxonomy( 'tax_name' ) shortcut method that will return a TaxonomiesRequest object pre-bound to the taxonomy with "tax_name".

Make the WP constructor return a WP client instance

Currently, the wp function is designed to be called each time a request is made. To enable passing the same configured WP client instance around between functions, we should make WP return a configured client object that can be used to instantiate new requests.

Before:

wp( options ).posts()...

Proposal:

var wp = new WP( options );
wp.posts()...

Support Auto-Discovery from top-level API root endpoint response

One major point of discussion at WCNYC surrounding the API is how to handle discoverability, as it was recommended to consider templated URLs (which are common with SAAS-provided APIs) to be an anti-pattern when the WP ReST API can be considered to be more of an API builder for individual sites than an API in and of itself. In a polymorphic world of individually-configured and customized WordPress installs, having non-templated paths that are all discovered from a root endpoint is the solution deemed most flexible and least likely to paint us into a corner.

Significant drawbacks to this approach:

  • A first request is always needed: There is no way to query for a resource without first interrogating the root endpoint for location information. You can safely assume you know how things are set up in most cases, but there's no way around this added request overhead if you're requesting content from a third-party site for the first time.
  • To avoid having to make a first request for all subsequent requests, the information from that request (an object defining available endpoints, from which all following requests will be derived) must be cached somehow. This is of minor concern in server-side applications, which can cache in-memory within the process, in session storage, or persistently in a database; but for client applications this data would need to be persisted in local storage or a similar client-side cache in order to avoid needing to be requested on every page load.

I find this concept appealing from the standpoint of conceptual flexibility, and in the way it forces you to confront resources very specifically as members of collections, but I still can't but think it would be extremely frustrating and limiting in implementation. I want to study more APIs and figure out if there's anything out there operating in the fashion described by @maxcutler, to evaluate the best way to work around these limitations and alleviate my concerns.

Queries should be scoped to a particular client object method call

It should be possible to run wp.posts() and wp.taxonomies() without the latter changing any properties or state of the former request. Top-level methods on the wp object should create and return a new query object that can be chained, rather than setting private properties on the wp instance itself.

Methods that will create query objects:

  • wp.posts
  • wp.pages
  • wp.users
  • wp.taxonomies
  • wp.categories (alias)
  • wp.tags (alias)
  • wp.types (?)

Get taxonomy by slug

In the docs (frontpage) it says:

wp.taxonomies().taxonomy( 'taxonomy_name' ).term( termIdentifier ): get the term with slug or ID termIdentifier from the taxonomy taxonomy_name

This works with the ID, but not with the slug :(. Is this something that changed?

HOST/wp-json/taxonomies/post_tag/terms/42 works
HOST/wp-json/taxonomies/post_tag/terms/sociaal-ondernemerschap doesn't

WP api version

It's not mentioned in the readme: is this driver for the v1 or v2 edition of the WP API plugin?

Find a utility library for constructing URIs

String concatenation works OK for simple endpoint route requests, but when we start getting into filters (see #18) the query structure's going to get significantly more complex. I would like to pull in a 3rd-party utility for constructing URIs with query parameters, etc; I've been looking at URI.js (link goes to the npm package), but I'm open to alternative suggestions.

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.