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.
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