ruby-vast 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +55 -0
- data/lib/ruby-vast.rb +1 -0
- data/lib/vast.rb +33 -0
- data/lib/vast/ad.rb +103 -0
- data/lib/vast/companion_creative.rb +81 -0
- data/lib/vast/creative.rb +62 -0
- data/lib/vast/document.rb +71 -0
- data/lib/vast/element.rb +9 -0
- data/lib/vast/extension.rb +18 -0
- data/lib/vast/icon.rb +87 -0
- data/lib/vast/inline_ad.rb +22 -0
- data/lib/vast/linear_creative.rb +57 -0
- data/lib/vast/mediafile.rb +56 -0
- data/lib/vast/non_linear_creative.rb +97 -0
- data/lib/vast/version.rb +3 -0
- data/lib/vast/wrapper_ad.rb +19 -0
- data/lib/vast3_draft.xsd +1036 -0
- data/lib/vast_2.0.1.xsd +643 -0
- data/test/ad_test.rb +122 -0
- data/test/companion_creative_test.rb +38 -0
- data/test/creative_test.rb +35 -0
- data/test/document_test.rb +72 -0
- data/test/examples/document_with_no_ads.xml +3 -0
- data/test/examples/document_with_one_inline_ad.xml +121 -0
- data/test/examples/document_with_one_inline_and_one_wrapper_ad.xml +105 -0
- data/test/examples/document_with_one_wrapper_ad.xml +40 -0
- data/test/examples/document_with_two_inline_ads.xml +120 -0
- data/test/examples/invalid_document.xml +3 -0
- data/test/examples/invalid_document_with_missing_ad_type.xml +58 -0
- data/test/examples/invalid_document_with_missing_creative_type.xml +39 -0
- data/test/examples/vast3_inline.xml +86 -0
- data/test/extension_test.rb +20 -0
- data/test/inline_ad_test.rb +14 -0
- data/test/linear_creative_test.rb +26 -0
- data/test/mediafile_test.rb +20 -0
- data/test/non_linear_creative_test.rb +39 -0
- data/test/test_helper.rb +13 -0
- data/test/vast3_inline_ad_test.rb +23 -0
- data/test/wrapper_ad_test.rb +16 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fb2a018e9a3eda6638792161504845821368d184
|
4
|
+
data.tar.gz: 527f23f4e0e39ca8e6602397d06ff25ef78e6417
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f1801bc2fa08103fea3b347e69d6961c174c3da0af92198678bf72cf5d6e84859a573e266d09cc5268a1f495080187ca931e4ad0117791312c840f324a634645
|
7
|
+
data.tar.gz: c4e662b14472de98ed5184189d025d25d49aea95e63256525c19b4bcfc6ff70a18fcc4757c55c8961a94ec3c7f2f31c0dcae14c69f77ee01180257d87b09f0cd
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Chris Dinn, 2017 Mike Mulev
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
= VAST
|
2
|
+
|
3
|
+
A library for parsing Digital Video Ad Serving Template (VAST) 2.0 XML documents, as outlined by the Interactive Advertising Bureau at
|
4
|
+
http://www.iab.net/iab_products_and_industry_services/508676/digitalvideo/vast. VAST outlines a standard document format for communication between digital video players and ad servers, including support for
|
5
|
+
multiple forms of creative and tracking support that conforms to the latest IAB standards.
|
6
|
+
|
7
|
+
It's recommended that you review the resources made available for the IAB. Whenever possible the documentation in this library has been
|
8
|
+
pulled directly from those resources.
|
9
|
+
|
10
|
+
This library strives to be as true to the standard as possible, while presenting a Ruby-friendly interface.
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
VAST is available as a RubyGem.
|
15
|
+
|
16
|
+
gem install vast
|
17
|
+
|
18
|
+
== Usage
|
19
|
+
|
20
|
+
Parse a VAST document and access its contents using an easy-to-understand model. For example:
|
21
|
+
|
22
|
+
document = VAST::Document.parse(File.read("vast_document.xml"))
|
23
|
+
inline_ad = document.inline_ads.first
|
24
|
+
puts inline_ad.linear_creative.mediafiles.first.type
|
25
|
+
=> "video/x-flv"
|
26
|
+
puts inline_ad.linear_creative.mediafiles.first.url
|
27
|
+
=> #<URI::HTTP:0x1015ad5f0 URL:http://creativeurl.ca/mediafile>
|
28
|
+
|
29
|
+
See the documentation for the individual classes for an overview of what information available for each class.
|
30
|
+
|
31
|
+
== Documentation
|
32
|
+
|
33
|
+
Read the rdoc at http://rdoc.info/projects/chrisdinn/vast
|
34
|
+
|
35
|
+
== Problems/Bugs/Requests
|
36
|
+
|
37
|
+
Please, file an issue.
|
38
|
+
|
39
|
+
== Running Tests
|
40
|
+
|
41
|
+
bundle exec rake
|
42
|
+
|
43
|
+
== Note on Patches/Pull Requests
|
44
|
+
|
45
|
+
* Fork the project.
|
46
|
+
* Make your feature addition or bug fix.
|
47
|
+
* Add tests for it. This is important so I don't break it in a
|
48
|
+
future version unintentionally.
|
49
|
+
* Commit, do not mess with rakefile, version, or history.
|
50
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
51
|
+
* Send me a pull request. Bonus points for topic branches.
|
52
|
+
|
53
|
+
== Copyright
|
54
|
+
|
55
|
+
© 2010 Chris Dinn. See LICENSE for details.
|
data/lib/ruby-vast.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'vast'
|
data/lib/vast.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require 'vast/document'
|
4
|
+
require 'vast/element'
|
5
|
+
require 'vast/ad'
|
6
|
+
require 'vast/inline_ad'
|
7
|
+
require 'vast/wrapper_ad'
|
8
|
+
require 'vast/creative'
|
9
|
+
require 'vast/linear_creative'
|
10
|
+
require 'vast/mediafile'
|
11
|
+
require 'vast/icon'
|
12
|
+
require 'vast/companion_creative'
|
13
|
+
require 'vast/non_linear_creative'
|
14
|
+
require 'vast/extension'
|
15
|
+
|
16
|
+
# This module wraps VAST documents, as outlined by the IAB at http://www.iab.net/media/file/VAST-2_0-FINAL.pdf
|
17
|
+
module VAST
|
18
|
+
VAST_VERSION = 2.0
|
19
|
+
VAST_SCHEMA_XSD_FILE = File.expand_path(File.join(File.dirname(__FILE__), 'vast_2.0.1.xsd'))
|
20
|
+
VAST3_SCHEMA_XSD_FILE = File.expand_path(File.join(File.dirname(__FILE__), 'vast3_draft.xsd'))
|
21
|
+
|
22
|
+
class VASTError < StandardError; end
|
23
|
+
|
24
|
+
# Raised when parsing a VAST document that does not conform to the VAST spec XSD
|
25
|
+
class InvalidDocumentError < VASTError; end
|
26
|
+
|
27
|
+
# Raised when parsing a VAST ad node that is not complete
|
28
|
+
class InvalidAdError < VASTError; end
|
29
|
+
|
30
|
+
# Raised when parsing a VAST creative node that is not complete
|
31
|
+
class InvalidCreativeError < VASTError; end
|
32
|
+
|
33
|
+
end
|
data/lib/vast/ad.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
module VAST
|
2
|
+
# Contains some combination of video, companions, and non-linear units for a single advertiser.
|
3
|
+
#
|
4
|
+
# A single VAST response may include multiple Ads from multiple advertisers. It will be up to
|
5
|
+
# the Video Player to determine the order, timing, placement, etc for the multiple ads. However,
|
6
|
+
# the player should generally respect the sequential order of the Ad elements within a VAST response.
|
7
|
+
#
|
8
|
+
# The VAST response does not contain information on the placement or timing of each ad. It is up
|
9
|
+
# to the Video Player to determine the optimal inclusion points of the ads.
|
10
|
+
#
|
11
|
+
# Can either be a InlineAd, meaning it contains all the elements necessary to display the
|
12
|
+
# visual experience, or a WrapperAd, which points to a downstream VAST document that must be
|
13
|
+
# requested from another server.
|
14
|
+
#
|
15
|
+
# An Ad may include one or more pieces of creative that are part of a single execution. For example, an Ad
|
16
|
+
# may include a linear video element with a set of companion banners; this would be reflected by two Creative
|
17
|
+
# elements, one LinearCreative and one CompanionCreative.
|
18
|
+
class Ad < Element
|
19
|
+
|
20
|
+
# Creates proper ad type
|
21
|
+
def self.create(node)
|
22
|
+
if node.at('InLine')
|
23
|
+
InlineAd.new(node)
|
24
|
+
elsif node.at('Wrapper')
|
25
|
+
WrapperAd.new(node)
|
26
|
+
else
|
27
|
+
raise InvalidAdError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Ad id, if indicated
|
32
|
+
def id
|
33
|
+
source_node[:id]
|
34
|
+
end
|
35
|
+
|
36
|
+
def sequence
|
37
|
+
source_node[:sequence]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns name of source ad server
|
41
|
+
def ad_system
|
42
|
+
ad_system_node = source_node.at("AdSystem")
|
43
|
+
if ad_system_node
|
44
|
+
ad_system_node.content
|
45
|
+
else
|
46
|
+
raise InvalidAdError, "missing AdSystem node in Ad"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns URI to request if ad does not play due to error.
|
51
|
+
def error_url
|
52
|
+
error_url_node = source_node.at("Error")
|
53
|
+
URI.parse(error_url_node.content.strip) if error_url_node
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns an array containing all linear creatives.
|
57
|
+
def linear_creatives
|
58
|
+
source_node.xpath('.//Creative/Linear').to_a.collect do |node|
|
59
|
+
LinearCreative.new(node)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# This is a convenience method for when only the first piece of linear creative is needed.
|
64
|
+
# It's common for an ad to contain only one piece of linear creative.
|
65
|
+
def linear_creative
|
66
|
+
linear_creatives.first
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns an array containing all non linear creatives.
|
70
|
+
def non_linear_creatives
|
71
|
+
source_node.xpath('.//Creative/NonLinearAds/NonLinear').to_a.collect do |node|
|
72
|
+
NonLinearCreative.new(node)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns an array containing all companion creatives.
|
77
|
+
def companion_creatives
|
78
|
+
source_node.xpath('.//Creative/CompanionAds/Companion').to_a.collect do |node|
|
79
|
+
CompanionCreative.new(node)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Each Ad must contain at least one impression.
|
84
|
+
def impression
|
85
|
+
URI.parse(source_node.at('Impression').content.strip)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Array of all impressions available for this ad, excluding those specific
|
89
|
+
# to a particular creative.
|
90
|
+
def impressions
|
91
|
+
source_node.xpath('.//Impression').to_a.collect do |node|
|
92
|
+
URI.parse(node.content.strip)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# All extensions included with this ad.
|
97
|
+
def extensions
|
98
|
+
source_node.xpath('.//Extension').to_a.collect do |node|
|
99
|
+
Extension.new(node)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module VAST
|
2
|
+
# Commonly text, display ads, rich media, or skins that wrap around the video experience. These ads come
|
3
|
+
# in a number of sizes and shapes and typically run alongside or surrounding the video player.
|
4
|
+
class CompanionCreative < Creative
|
5
|
+
|
6
|
+
def id
|
7
|
+
source_node[:id]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Width in pixels of companion
|
11
|
+
def width
|
12
|
+
source_node[:width].to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
# Height in pixels of companion
|
16
|
+
def height
|
17
|
+
source_node[:height].to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
# Width in pixels of expanding companion ad when in expanded state
|
21
|
+
def expanded_width
|
22
|
+
source_node[:expandedWidth].to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
# Height in pixels of expanding companion ad when in expanded state
|
26
|
+
def expanded_height
|
27
|
+
source_node[:expandedHeight].to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
# Defines the method to use for communication with the companion
|
31
|
+
def api_framework
|
32
|
+
source_node[:apiFramework]
|
33
|
+
end
|
34
|
+
|
35
|
+
# URI to open as destination page when user clicks on the video
|
36
|
+
def click_through_url
|
37
|
+
URI.parse source_node.at('CompanionClickThrough').content.strip
|
38
|
+
end
|
39
|
+
|
40
|
+
# Alternate text to be displayed when companion is rendered in HTML environment.
|
41
|
+
def alt_text
|
42
|
+
node = source_node.at('AltText')
|
43
|
+
node.nil? ? nil : node.content
|
44
|
+
end
|
45
|
+
|
46
|
+
# Type of companion resource, returned as a symbol. Either :static, :iframe, or :html.
|
47
|
+
def resource_type
|
48
|
+
if source_node.at('StaticResource')
|
49
|
+
:static
|
50
|
+
elsif source_node.at('IFrameResource')
|
51
|
+
:iframe
|
52
|
+
elsif source_node.at('HTMLResource')
|
53
|
+
:html
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns MIME type of static creative
|
58
|
+
def creative_type
|
59
|
+
if resource_type == :static
|
60
|
+
source_node.at('StaticResource')[:creativeType]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns URI for static or iframe resource
|
65
|
+
def resource_url
|
66
|
+
case resource_type
|
67
|
+
when :static
|
68
|
+
URI.parse source_node.at('StaticResource').content.strip
|
69
|
+
when :iframe
|
70
|
+
URI.parse source_node.at('IFrameResource').content.strip
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns HTML text for html resource
|
75
|
+
def resource_html
|
76
|
+
if resource_type == :html
|
77
|
+
source_node.at('HTMLResource').content
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module VAST
|
2
|
+
# Contains the information related to a piece of creative.
|
3
|
+
#
|
4
|
+
# === Sequence
|
5
|
+
# The Creative element takes an optional “sequence” attribute that indicates the suggested order in which the
|
6
|
+
# Creatives should be displayed. If two Creative elements are intended to be shown at the same time they should
|
7
|
+
# share the same sequence number.
|
8
|
+
class Creative < Element
|
9
|
+
|
10
|
+
def ad
|
11
|
+
Ad.create source_node.ancestors('Ad').first
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
creative_node[:id]
|
16
|
+
end
|
17
|
+
|
18
|
+
def ad_id
|
19
|
+
creative_node[:AdID]
|
20
|
+
end
|
21
|
+
|
22
|
+
# The preferred order in which multiple Creatives should be displayed
|
23
|
+
def sequence
|
24
|
+
creative_node[:sequence]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Data to be passed into the video ad.
|
28
|
+
def ad_parameters
|
29
|
+
source_node.at('AdParameters').content
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a hash, keyed by event name, containing an array of URIs to be called for each event.
|
33
|
+
def tracking_urls
|
34
|
+
tracking_urls = {}
|
35
|
+
source_node.xpath('.//Tracking').to_a.collect do |node|
|
36
|
+
underscored_name = underscore(node[:event])
|
37
|
+
if tracking_urls[underscored_name.to_sym]
|
38
|
+
tracking_urls[underscored_name.to_sym] << URI.parse(node.content.strip)
|
39
|
+
else
|
40
|
+
tracking_urls[underscored_name.to_sym] = [URI.parse(node.content.strip)]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
tracking_urls
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def underscore(camel_cased_word)
|
49
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
50
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
51
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
52
|
+
tr("-", "_").
|
53
|
+
downcase
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def creative_node
|
59
|
+
source_node.ancestors('Creative').first
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module VAST
|
2
|
+
# A complete VAST document
|
3
|
+
class Document < Nokogiri::XML::Document
|
4
|
+
@macros = ["ERRORCODE", "CONTENTPLAYHEAD", "CACHEBUSTING", "ASSETURI"]
|
5
|
+
|
6
|
+
def self.eval_macro(xml_string)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Parse a VAST XML document
|
10
|
+
def self.parse(*args)
|
11
|
+
# VAST 3 support macro, need to uri escape all macros
|
12
|
+
if (args[0].is_a? String)
|
13
|
+
@macros.each{|x| args[0].gsub!("[#{x}]", "%5B#{x}%5D")}
|
14
|
+
end
|
15
|
+
super(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Same as parse, but raises InvalidDocumentError if document is not valid
|
19
|
+
def self.parse!(*args)
|
20
|
+
document = parse(*args)
|
21
|
+
raise InvalidDocumentError unless document.valid?
|
22
|
+
document
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks whether document conforms to the VAST XML Schema Definitions, accessible at
|
26
|
+
# http://www.iab.net/iab_products_and_industry_services/508676/digitalvideo/vast
|
27
|
+
def valid?
|
28
|
+
version_node = self.xpath('/VAST/@version').first
|
29
|
+
major_version = version_node ? version_node.value.to_i : 0
|
30
|
+
case major_version
|
31
|
+
when 2
|
32
|
+
xsd_file = VAST_SCHEMA_XSD_FILE
|
33
|
+
when 3
|
34
|
+
xsd_file = VAST3_SCHEMA_XSD_FILE
|
35
|
+
else
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
xsd = Nokogiri::XML::Schema(File.read(xsd_file))
|
39
|
+
xsd.valid?(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
# A single VAST response may include multiple Ads from multiple advertisers. It will be up to the
|
43
|
+
# Video Player to determine the order, timing, placement, etc for the multiple ads. However, the
|
44
|
+
# player should generally respect the sequential order of the Ad elements within the ad.
|
45
|
+
#
|
46
|
+
# If no ads of any type are available, it would be indicated by the absence of any ads.
|
47
|
+
def ads
|
48
|
+
self.root.xpath('.//Ad').to_a.collect do |node|
|
49
|
+
Ad.create(node)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# All inline ads
|
54
|
+
def inline_ads
|
55
|
+
ads.select{ |ad| ad.kind_of?(VAST::InlineAd) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def ad_pod_ads
|
59
|
+
ads.select{ |ad| ad.sequence.is_a? Numeric }.sort{|x, y| x.sequence <=> y.sequence }
|
60
|
+
end
|
61
|
+
|
62
|
+
def stand_alone_ads
|
63
|
+
ads.select{ |ad| !ad.sequence.is_a?(Numeric) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# All wrapper ads
|
67
|
+
def wrapper_ads
|
68
|
+
ads.select{ |ad| ad.kind_of?(VAST::WrapperAd) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|