ruby-vast 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +55 -0
  4. data/lib/ruby-vast.rb +1 -0
  5. data/lib/vast.rb +33 -0
  6. data/lib/vast/ad.rb +103 -0
  7. data/lib/vast/companion_creative.rb +81 -0
  8. data/lib/vast/creative.rb +62 -0
  9. data/lib/vast/document.rb +71 -0
  10. data/lib/vast/element.rb +9 -0
  11. data/lib/vast/extension.rb +18 -0
  12. data/lib/vast/icon.rb +87 -0
  13. data/lib/vast/inline_ad.rb +22 -0
  14. data/lib/vast/linear_creative.rb +57 -0
  15. data/lib/vast/mediafile.rb +56 -0
  16. data/lib/vast/non_linear_creative.rb +97 -0
  17. data/lib/vast/version.rb +3 -0
  18. data/lib/vast/wrapper_ad.rb +19 -0
  19. data/lib/vast3_draft.xsd +1036 -0
  20. data/lib/vast_2.0.1.xsd +643 -0
  21. data/test/ad_test.rb +122 -0
  22. data/test/companion_creative_test.rb +38 -0
  23. data/test/creative_test.rb +35 -0
  24. data/test/document_test.rb +72 -0
  25. data/test/examples/document_with_no_ads.xml +3 -0
  26. data/test/examples/document_with_one_inline_ad.xml +121 -0
  27. data/test/examples/document_with_one_inline_and_one_wrapper_ad.xml +105 -0
  28. data/test/examples/document_with_one_wrapper_ad.xml +40 -0
  29. data/test/examples/document_with_two_inline_ads.xml +120 -0
  30. data/test/examples/invalid_document.xml +3 -0
  31. data/test/examples/invalid_document_with_missing_ad_type.xml +58 -0
  32. data/test/examples/invalid_document_with_missing_creative_type.xml +39 -0
  33. data/test/examples/vast3_inline.xml +86 -0
  34. data/test/extension_test.rb +20 -0
  35. data/test/inline_ad_test.rb +14 -0
  36. data/test/linear_creative_test.rb +26 -0
  37. data/test/mediafile_test.rb +20 -0
  38. data/test/non_linear_creative_test.rb +39 -0
  39. data/test/test_helper.rb +13 -0
  40. data/test/vast3_inline_ad_test.rb +23 -0
  41. data/test/wrapper_ad_test.rb +16 -0
  42. metadata +99 -0
@@ -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.
@@ -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.
@@ -0,0 +1 @@
1
+ require 'vast'
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ module VAST
2
+ class Element
3
+ attr_reader :source_node
4
+
5
+ def initialize(node)
6
+ @source_node = node
7
+ end
8
+ end
9
+ end