#!/usr/bin/python2
#
# Copyright (C) 2010-2011 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""GData definitions for Content API for Shopping"""


__author__ = 'afshar (Ali Afshar), dhermes (Daniel Hermes)'


import atom.core
import atom.data
import atom.http_core
import gdata.data


ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom'
GD_NAMESPACE = 'http://schemas.google.com/g/2005'
GD_NAMESPACE_TEMPLATE = '{http://schemas.google.com/g/2005}%s'
SC_NAMESPACE_TEMPLATE = ('{http://schemas.google.com/'
                        'structuredcontent/2009}%s')
SCP_NAMESPACE_TEMPLATE = ('{http://schemas.google.com/'
                         'structuredcontent/2009/products}%s')

# Content API for Shopping, general (sc) attributes

class ProductId(atom.core.XmlElement):
  """sc:id element

  It is required that all inserted products are provided with a unique
  alphanumeric ID, in this element.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'id'


class ImageLink(atom.core.XmlElement):
  """sc:image_link element

  This is the URL of an associated image for a product. Please use full size
  images (400x400 pixels or larger), not thumbnails.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'image_link'


class AdditionalImageLink(atom.core.XmlElement):
  """sc:additional_image_link element

  The URLs of any additional images for the product. This tag may be repeated.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'additional_image_link'


class Channel(atom.core.XmlElement):
  """
  sc:channel element

  The channel for the product. Supported values are: 'online', 'local'
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'channel'


class ContentLanguage(atom.core.XmlElement):
  """
  sc:content_language element

  Language used in the item content for the product
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'content_language'


class TargetCountry(atom.core.XmlElement):
  """
  sc:target_country element

  The target country of the product
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'target_country'


class ExpirationDate(atom.core.XmlElement):
  """sc:expiration_date

  This is the date when the product listing will expire. If omitted, this will
  default to 30 days after the product was created.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'expiration_date'


class Adult(atom.core.XmlElement):
  """sc:adult element

  Indicates whether the content is targeted towards adults, with possible
  values of "true" or "false". Defaults to "false".
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'adult'


class Attribute(atom.core.XmlElement):
  """sc:attribute element

  Generic attribute used for generic projection and to define
  custom elements.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'attribute'
  name = 'name'
  type = 'type'
  unit = 'unit'


class Group(atom.core.XmlElement):
  """sc:group element

  Generic group used for generic projection and to define
  custom elements groups.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'group'
  name = 'name'
  attribute = [Attribute]


# Destination Attributes (to be used with app:control element)

class RequiredDestination(atom.core.XmlElement):
  """sc:required_destination element

  This element defines the required destination for a product, namely
  "ProductSearch", "ProductAds" or "CommerceSearch". It should be added to the
  app:control element (ProductEntry's "control" attribute) to specify where the
  product should appear in search APIs.

  By default, when omitted, the api attempts to upload to as many destinations
  as possible.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'required_destination'
  dest = 'dest'


class ValidateDestination(atom.core.XmlElement):
  """sc:validate_destination element

  This element defines the validate destination for a product, namely
  "ProductSearch", "ProductAds" or "CommerceSearch". It should be added to the
  app:control element (ProductEntry's "control" attribute) to specify which the
  destinations you would like error info for.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'validate_destination'
  dest = 'dest'


class ExcludedDestination(atom.core.XmlElement):
  """sc:excluded_destination element

  This element defines the required destination for a product, namely
  "ProductSearch", "ProductAds" or "CommerceSearch". It should be added to the
  app:control element (ProductEntry's "control" attribute) to specify where the
  product should not appear in search APIs.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'excluded_destination'
  dest = 'dest'


class Status(atom.core.XmlElement):
  """sc:status element

  This element defines the status of an element in a particular destination. It
  has a destination and an actual status such as enlisted, review, or
  disapproved.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'status'
  dest = 'dest'
  status = 'status'


# Warning Attributes (to be used with app:control element)

class Code(atom.core.XmlElement):
  """sc:code element

  The warning code. Currently validation/missing_recommended is the
  only code used.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'code'


class Domain(atom.core.XmlElement):
  """sc:domain element

  The scope of the warning. A comma-separated list of destinations,
  for example: ProductSearch.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'domain'


class Location(atom.core.XmlElement):
  """sc:location element

  The name of the product element that has raised the warning. This may be
  any valid product element.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'location'


class Message(atom.core.XmlElement):
  """sc:message element

  A plain text description of the warning.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'message'


class WarningElement(atom.core.XmlElement):
  """sc:warning element

  Container element for an individual warning.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'warning'
  code = Code
  domain = Domain
  location = Location
  message = Message


class Warnings(atom.core.XmlElement):
  """sc:warnings element

  Container element for the list of warnings.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'warnings'
  warnings = [WarningElement]


class Datapoint(atom.core.XmlElement):
  """sc:datapoint element

  Datapoint representing click data for an item on
  a given day.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'datapoint'
  clicks = 'clicks'
  date = 'date'
  paid_clicks = 'paid_clicks'


class Performance(atom.core.XmlElement):
  """sc:performance element

  Container element for daily click data.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'performance'
  datapoint = [Datapoint]


class ProductControl(atom.data.Control):
  """
  app:control element

  overridden to provide additional elements in the sc namespace.
  """
  _qname = atom.data.Control._qname[1]
  required_destination = [RequiredDestination]
  validate_destination = [ValidateDestination]
  excluded_destination = [ExcludedDestination]
  status = [Status]
  warnings = Warnings

# Content API for Shopping, product (scp) attributes

class Author(atom.core.XmlElement):
  """
  scp:author element

  Defines the author of the information, recommended for books.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'author'


class Availability(atom.core.XmlElement):
  """
  scp:availability element

  The retailer's suggested label for product availability. Supported values
  include: 'in stock', 'out of stock', 'limited availability',
           'available for order', and 'preorder'.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'availability'


class Brand(atom.core.XmlElement):
  """
  scp:brand element

  The brand of the product
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'brand'


class Color(atom.core.XmlElement):
  """scp:color element

  The color of the product.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'color'


class Condition(atom.core.XmlElement):
  """scp:condition element

  The condition of the product, one of "new", "used", "refurbished"
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'condition'


class Edition(atom.core.XmlElement):
  """scp:edition element

  The edition of the product. Recommended for products with multiple editions
  such as collectors' editions etc, such as books.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'edition'


class Feature(atom.core.XmlElement):
  """scp:feature element

  A product feature. A product may have multiple features, each being text, for
  example a smartphone may have features: "wifi", "gps" etc.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'feature'


class FeaturedProduct(atom.core.XmlElement):
  """scp:featured_product element

  Used to indicate that this item is a special, featured product; Supported
  values are: "true", "false".
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'featured_product'


class Gender(atom.core.XmlElement):
  """scp:gender element

  The gender for the item. Supported values are: 'unisex', 'female', 'male'

  Note: This tag is required if the google product type is part of
        Apparel & Accessories.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'gender'


class Genre(atom.core.XmlElement):
  """scp:genre element

  Describes the genre of a product, eg "comedy". Strongly recommended for media.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'genre'


class GoogleProductCategory(atom.core.XmlElement):
  """scp:google_product_category element

  The product's google category. The value must be one of the categories listed
  in the Product type taxonomy, which can be found at
  http://www.google.com/support/merchants/bin/answer.py?answer=160081.

  Note that & and > characters must be encoded as &amp; and &gt;
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'google_product_category'


class Gtin(atom.core.XmlElement):
  """scp:gtin element

  GTIN of the product (isbn/upc/ean)
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'gtin'


class ItemGroupID(atom.core.XmlElement):
  """scp:item_group_id element

  The identifier for products with variants. This id is used to link items which
  have different values for the fields:

      'color', 'material', 'pattern', 'size'

  but are the same item, for example a shirt with different sizes.

  Note: This tag is required for all product variants.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'item_group_id'


class Manufacturer(atom.core.XmlElement):
  """scp:manufacturer element

  Manufacturer of the product.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'manufacturer'


class Material(atom.core.XmlElement):
  """scp:material element

  The material the product is made of.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'material'


class Mpn(atom.core.XmlElement):
  """scp:mpn element

  Manufacturer's Part Number. A unique code determined by the manufacturer for
  the product.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'mpn'


class Pattern(atom.core.XmlElement):
  """scp:pattern element

  The pattern of the product. (e.g. polka dots)
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'pattern'


class Price(atom.core.XmlElement):
  """scp:price element

  The price of the product. The unit attribute must be set, and should represent
  the currency.

  Note: Required Element
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'price'
  unit = 'unit'


class ProductType(atom.core.XmlElement):
  """scp:product_type element

  Describes the type of product. A taxonomy of available product types is
  listed at http://www.google.com/basepages/producttype/taxonomy.txt and the
  entire line in the taxonomy should be included, for example "Electronics >
  Video > Projectors".
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'product_type'


class Quantity(atom.core.XmlElement):
  """scp:quantity element

  The number of items available. A value of 0 indicates items that are
  currently out of stock.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'quantity'


class ShippingPrice(atom.core.XmlElement):
  """scp:shipping_price element

  Fixed shipping price, represented as a number. Specify the currency as the
  "unit" attribute".

  This element should be placed inside the scp:shipping element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_price'
  unit = 'unit'


class ShippingCountry(atom.core.XmlElement):
  """scp:shipping_country element

  The two-letter ISO 3166 country code for the country to which an item will
  ship.

  This element should be placed inside the scp:shipping element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_country'


class ShippingRegion(atom.core.XmlElement):
  """scp:shipping_region element

  The geographic region to which a shipping rate applies, e.g., in the US, the
  two-letter state abbreviation, ZIP code, or ZIP code range using * wildcard.

  This element should be placed inside the scp:shipping element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_region'


class ShippingService(atom.core.XmlElement):
  """scp:shipping_service element

  A free-form description of the service class or delivery speed.

  This element should be placed inside the scp:shipping element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_service'


class Shipping(atom.core.XmlElement):
  """scp:shipping element

  Container for the shipping rules as provided by the shipping_country,
  shipping_price, shipping_region and shipping_service tags.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'shipping'
  shipping_price = ShippingPrice
  shipping_country = ShippingCountry
  shipping_service = ShippingService
  shipping_region = ShippingRegion


class ShippingWeight(atom.core.XmlElement):
  """scp:shipping_weight element

  The shipping weight of a product. Requires a value and a unit using the unit
  attribute. Valid units include lb, pound, oz, ounce, g, gram, kg, kilogram.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'shipping_weight'
  unit = 'unit'


class Size(atom.core.XmlElement):
  """scp:size element

  Available sizes of an item.  Appropriate values include: "small", "medium",
  "large", etc. The product enttry may contain multiple sizes, to indicate the
  available sizes.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'size'


class TaxRate(atom.core.XmlElement):
  """scp:tax_rate element

  The tax rate as a percent of the item price, i.e., number, as a percentage.

  This element should be placed inside the scp:tax (Tax class) element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'tax_rate'


class TaxCountry(atom.core.XmlElement):
  """scp:tax_country element

  The country an item is taxed in (as a two-letter ISO 3166 country code).

  This element should be placed inside the scp:tax (Tax class) element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'tax_country'


class TaxRegion(atom.core.XmlElement):
  """scp:tax_region element

  The geographic region that a tax rate applies to, e.g., in the US, the
  two-letter state abbreviation, ZIP code, or ZIP code range using * wildcard.

  This element should be placed inside the scp:tax (Tax class) element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'tax_region'


class TaxShip(atom.core.XmlElement):
  """scp:tax_ship element

  Whether tax is charged on shipping for this product. The default value is
  "false".

  This element should be placed inside the scp:tax (Tax class) element.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'tax_ship'


class Tax(atom.core.XmlElement):
  """scp:tax element

  Container for the tax rules for this product. Containing the tax_rate,
  tax_country, tax_region, and tax_ship elements
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'tax'
  tax_rate = TaxRate
  tax_country = TaxCountry
  tax_region = TaxRegion
  tax_ship = TaxShip


class Year(atom.core.XmlElement):
  """scp:year element

  The year the product was produced. Expects four digits
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'year'


class IdentifierExists(atom.core.XmlElement):
  """scp:identifier_exists element

  Specify as true if the item has no manufacturer part number or
  any other industry standard product identifier.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'identifier_exists'


class UnitPricingMeasure(atom.core.XmlElement):
  """scp:unit_pricing_measure element

  The dimension by which the item is sold.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'unit_pricing_measure'
  unit = 'unit'


class UnitPricingBaseMeasure(atom.core.XmlElement):
  """scp:unit_pricing_base_measure element

  Your preference of the denominator of the unit price.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'unit_pricing_base_measure'
  unit = 'unit'


class EnergyEfficiencyClass(atom.core.XmlElement):
  """scp:energy_efficiency_class element

  The item's energy efficiency class.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'energy_efficiency_class'


class Multipack(atom.core.XmlElement):
  """scp:ultipack element

  The number of products in a merchant-defined custom multipack
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'multipack'


class ProductEntry(gdata.data.BatchEntry):
  """Product entry containing product information

  The elements of this entry that are used are made up of five different
  namespaces. They are:

  atom: - Atom
  app: - Atom Publishing Protocol
  gd: - Google Data API
  sc: - Content API for Shopping, general attributes
  scp: - Content API for Shopping, product attributes

  Only the sc and scp namespace elements are defined here, but additional useful
  elements are defined in superclasses.

  The following attributes are encoded as XML elements in the Atomn (atom:)
  namespace: title, link, entry, id, category, content, author, created
  updated. Among these, the title, content and link tags are part of the
  required Content for Shopping API so we document them here.

  .. attribute:: title

    The title of the product.

    This should be a :class:`atom.data.Title` element, for example::

      entry = ProductEntry()
      entry.title = atom.data.Title(u'32GB MP3 Player')

  .. attribute:: content

    The description of the item.

    This should be a :class:`atom.data.Content` element, for example::

      entry = ProductEntry()
      entry.content = atom.data.Content('My item description')

  .. attribute:: link

    A link to a page where the item can be purchased.

    This should be a :class:`atom.data.Link` element, for example::

      link = atom.data.Link(rel='alternate', type='text/html',
                            href='http://www.somehost.com/123456jsh9')
      entry = ProductEntry()
      entry.link.append(link)

  .. attribute:: additional_image_link

    A list of additional links to images of the product. Each link should be an
    :class:`AdditionalImageLink` element, for example::

      entry = ProductEntry()
      entry.additional_image_link.append(
          AdditionalImageLink('http://myshop/cdplayer.jpg'))

  .. attribute:: author

    The author of the product.

    This should be a :class:`Author` element, for example::

      entry = ProductEntry()
      entry.author = atom.data.Author(u'Isaac Asimov')

  .. attribute:: attribute

    List of generic attributes.

    This should be a list of :class:`Attribute` elements, for example::

      attribute = Attribute('foo')
      attribute.name = 'bar'
      attribute.type = 'baz'
      attribute.unit = 'kg'
      entry = ProductEntry()
      entry.attributes.append(attribute)

  .. attribute:: availability

    The avilability of a product.

    This should be an :class:`Availability` instance, for example::

      entry = ProductEntry()
      entry.availability = Availability('in stock')

  .. attribute:: brand

    The brand of a product.

    This should be a :class:`Brand` element, for example::

      entry = ProductEntry()
      entry.brand = Brand(u'Sony')

  .. attribute:: channel

    The channel for the product. Supported values are: 'online', 'local'

    This should be a :class:`Channel` element, for example::

      entry = ProductEntry()
      entry.channel = Channel('online')

  .. attribute:: color

    The color of a product.

    This should be a :class:`Color` element, for example::

      entry = ProductEntry()
      entry.color = Color(u'purple')

  .. attribute:: condition

    The condition of a product.

    This should be a :class:`Condition` element, for example::

      entry = ProductEntry()
      entry.condition = Condition(u'new')

  .. attribute:: content_language

    The language for the product.

    This should be a :class:`ContentLanguage` element, for example::

      entry = ProductEntry()
      entry.content_language = ContentLanguage('EN')

  .. attribute:: control

    Overrides :class:`atom.data.Control` to provide additional elements
    required_destination and excluded_destination in the sc namespace

    This should be a :class:`ProductControl` element.

  .. attribute:: edition

    The edition of the product.

    This should be a :class:`Edition` element, for example::

      entry = ProductEntry()
      entry.edition = Edition('1')

  .. attribute:: expiration_date

    The expiration date of this product listing.

    This should be a :class:`ExpirationDate` element, for example::

      entry = ProductEntry()
      entry.expiration_date = ExpirationDate('2011-22-03')

  .. attribute:: feature

    A list of features for this product.

    Each feature should be a :class:`Feature` element, for example::

      entry = ProductEntry()
      entry.feature.append(Feature(u'wifi'))
      entry.feature.append(Feature(u'gps'))

  .. attribute:: featured_product

    Whether the product is featured.

    This should be a :class:`FeaturedProduct` element, for example::

      entry = ProductEntry()
      entry.featured_product = FeaturedProduct('true')

  .. attribute:: gender

    The gender for the item. Supported values are: 'unisex', 'female', 'male'

    This should be a :class:`Gender` element, for example::

      entry = ProductEntry()
      entry.gender = Gender('female')

  .. attribute:: genre

    The genre of the product.

    This should be a :class:`Genre` element, for example::

      entry = ProductEntry()
      entry.genre = Genre(u'comedy')

  .. attribute:: google_product_category

    The product's google category. Value must come from taxonomy listed at

      http://www.google.com/support/merchants/bin/answer.py?answer=160081

    This should be a :class:`GoogleProductCategory` element, for example::

      entry = ProductEntry()
      entry.google_product_category = GoogleProductCategory(
          'Animals &gt; Live Animals')

  .. attribute:: gtin

    The gtin for this product.

    This should be a :class:`Gtin` element, for example::

      entry = ProductEntry()
      entry.gtin = Gtin('A888998877997')

  .. attribute:: image_link

    A link to the product image. This link should be an
    :class:`ImageLink` element, for example::

      entry = ProductEntry()
      entry.image_link = ImageLink('http://myshop/cdplayer.jpg')

  .. attribute:: item_group_id

    The identifier for products with variants. This id is used to link items
    which have different values for the fields:

      'color', 'material', 'pattern', 'size'

    but are the same item, for example a shirt with different sizes.

    This should be a :class:`ItemGroupID` element, for example::

      entry = ProductEntry()
      entry.item_group_id = ItemGroupID('R1726122')

  .. attribute:: manufacturer

    The manufacturer of the product.

    This should be a :class:`Manufacturer` element, for example::

      entry = ProductEntry()
      entry.manufacturer = Manufacturer('Sony')

  .. attribute:: material

    The material the product is made of.

    This should be a :class:`Material` element, for example::

      entry = ProductEntry()
      entry.material = Material('cotton')

  .. attribute:: mpn

    The manufacturer's part number for this product.

    This should be a :class:`Mpn` element, for example::

      entry = ProductEntry()
      entry.mpn = Mpn('cd700199US')

  .. attribute:: pattern

    The pattern of the product.

    This should be a :class:`Pattern` element, for example::

      entry = ProductEntry()
      entry.pattern = Pattern('polka dots')

  .. attribute:: performance

    The performance of the product.

    This should be a :class:`Performance` element.

  .. attribute:: price

    The price for this product.

    This should be a :class:`Price` element, including a unit argument to
    indicate the currency, for example::

      entry = ProductEntry()
      entry.price = Price('20.00', unit='USD')

  .. attribute:: product_id

    A link to the product image. This link should be an
    :class:`ProductId` element, for example::

      entry = ProductEntry()
      entry.product_id = ProductId('ABC1234')

  .. attribute:: product_type

    The type of product.

    This should be a :class:`ProductType` element, for example::

      entry = ProductEntry()
      entry.product_type = ProductType("Electronics > Video > Projectors")

  .. attribute:: quantity

    The quantity of product available in stock.

    This should be a :class:`Quantity` element, for example::

      entry = ProductEntry()
      entry.quantity = Quantity('100')

  .. attribute:: shipping

    The shipping rules for the product.

    This should be a :class:`Shipping` with the necessary rules embedded as
    elements, for example::

      shipping = Shipping()
      shipping.shipping_price = ShippingPrice('10.00', unit='USD')
      entry = ProductEntry()
      entry.shipping.append(shipping)

  .. attribute:: shipping_weight

    The shipping weight for this product.

    This should be a :class:`ShippingWeight` element, including a unit parameter
    for the unit of weight, for example::

      entry = ProductEntry()
      entry.shipping_weight = ShippingWeight('10.45', unit='kg')

  .. attribute:: size

    A list of the available sizes for this product.

    Each item in this list should be a :class:`Size` element, for example::

      entry = ProductEntry()
      entry.size.append(Size('Small'))
      entry.size.append(Size('Medium'))
      entry.size.append(Size('Large'))

  .. attribute:: target_country

    The target country for the product.

    This should be a :class:`TargetCountry` element, for example::

      entry = ProductEntry()
      entry.target_country = TargetCountry('US')

  .. attribute:: tax

    The tax rules for this product.

    This should be a :class:`Tax` element, with the tax rule elements embedded
    within, for example::

      tax = Tax()
      tax.tax_rate = TaxRate('17.5')
      entry = ProductEntry()
      entry.tax.append(tax)

  .. attribute:: year

    The year the product was created.

    This should be a :class:`Year` element, for example::

      entry = ProductEntry()
      entry.year = Year('2001')
  """

  additional_image_link = [AdditionalImageLink]
  author = Author
  attribute = [Attribute]
  availability = Availability
  brand = Brand
  channel = Channel
  color = Color
  condition = Condition
  content_language = ContentLanguage
  control = ProductControl
  edition = Edition
  expiration_date = ExpirationDate
  feature = [Feature]
  featured_product = FeaturedProduct
  gender = Gender
  genre = Genre
  google_product_category = GoogleProductCategory
  group = [Group]
  gtin = Gtin
  image_link = ImageLink
  item_group_id = ItemGroupID
  manufacturer = Manufacturer
  material = Material
  mpn = Mpn
  pattern = Pattern
  performance = Performance
  price = Price
  product_id = ProductId
  product_type = ProductType
  quantity = Quantity
  shipping = [Shipping]
  shipping_weight = ShippingWeight
  size = [Size]
  target_country = TargetCountry
  tax = [Tax]
  year = Year
  identifier_exists = IdentifierExists
  unit_pricing_measure = UnitPricingMeasure
  unit_pricing_base_measure = UnitPricingBaseMeasure
  energy_efficiency_class = EnergyEfficiencyClass
  multipack = Multipack

  def get_batch_errors(self):
    """Attempts to parse errors from atom:content element.

    If the atom:content element is type application/vnd.google.gdata.error+xml,
    then it will contain a gd:errors block.

    Returns:
      If the type of the content element is not
          'application/vnd.google.gdata.error+xml', or 0 or more than 1
          gd:errors elements are found within the <content type='app...'> block,
          then None is returned. Other wise, the gd:errors element parsed
          as a ContentForShoppingErrors object is returned.
    """
    if self.content.type == 'application/vnd.google.gdata.error+xml':
      errors_elements = self.content.get_elements(tag='errors',
                                                  namespace=GD_NAMESPACE)
      if len(errors_elements) == 1:
        errors_block = errors_elements[0]
        return atom.core.parse(errors_block.to_string(),
                               ContentForShoppingErrors)
    return None

  GetBatchErrors = get_batch_errors


class ErrorDomain(atom.core.XmlElement):
  """gd:domain element

  The scope of the error. If the error is global (e.g. missing title) then sc
  value is returned. Otherwise a comma-separated list of destinations is
  returned.

  This element should be placed inside the gd:error (ContentForShoppingError)
  element.
  """
  _qname = GD_NAMESPACE_TEMPLATE % 'domain'


class ErrorCode(atom.core.XmlElement):
  """gd:code element

  A code to categorize the errors.

  This element should be placed inside the gd:error (ContentForShoppingError)
  element.
  """
  _qname = GD_NAMESPACE_TEMPLATE % 'code'


class ErrorLocation(atom.core.XmlElement):
  """gd:location element

  The name of the attribute that failed validation.

  This element should be placed inside the gd:error (ContentForShoppingError)
  element.
  """
  _qname = GD_NAMESPACE_TEMPLATE % 'location'
  type = 'type'


class InternalReason(atom.core.XmlElement):
  """gd:internalReason element

  A more detailed message to explain the cause of the error.

  This element should be placed inside the gd:error (ContentForShoppingError)
  element.
  """
  _qname = GD_NAMESPACE_TEMPLATE % 'internalReason'


class ContentForShoppingError(atom.core.XmlElement):
  """gd:error element

  This element should be placed inside the gd:errors (ContentForShoppingErrors)
  element.
  """
  _qname = GD_NAMESPACE_TEMPLATE % 'error'
  domain = ErrorDomain
  code = ErrorCode
  location = ErrorLocation
  internal_reason = InternalReason

  @property
  def id(self):
    """Id for error element.

    The namespace for the id element is different in batch requests than in
    individual requests, so we need to consider either case.
    """
    for element in self.GetElements():
      if element.tag == 'id':
        if element.namespace in (GD_NAMESPACE, ATOM_NAMESPACE):
          return element.text

    return None


class ContentForShoppingErrors(atom.core.XmlElement):
  """The gd:errors element."""
  _qname = GD_NAMESPACE_TEMPLATE % 'errors'
  errors = [ContentForShoppingError]


# opensearch needs overriding for wrong version
# see http://code.google.com/p/gdata-python-client/issues/detail?id=483
class TotalResults(gdata.data.TotalResults):

    _qname = gdata.data.TotalResults._qname[1]


class ItemsPerPage(gdata.data.ItemsPerPage):

    _qname = gdata.data.ItemsPerPage._qname[1]


class StartIndex(gdata.data.StartIndex):

    _qname = gdata.data.StartIndex._qname[1]


class ProductFeed(gdata.data.BatchFeed):
  """Represents a feed of a merchant's products."""
  entry = [ProductEntry]
  total_results = TotalResults
  items_per_page = ItemsPerPage
  start_index = StartIndex

  def get_start_token(self):
    """Attempts to parse start-token from rel="next" link.

    A products feed may contain a rel="next" atom:link and the
    href contained in the link may have a start-token query parameter.

    Returns:
      If there is no rel="next" link or the rel="next" link doesn't contain
      the start-token query parameter, None is returned. Otherwise, the
      string value of the start-token query parameter is returned.
    """
    next_link = self.get_next_link()
    if next_link is not None:
      uri = atom.http_core.parse_uri(next_link.href)
      if 'start-token' in uri.query:
        return uri.query['start-token']
    return None

  GetStartToken = get_start_token


class Edited(atom.core.XmlElement):
  """sc:edited element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'edited'


class AttributeLanguage(atom.core.XmlElement):
  """sc:attribute_language element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'attribute_language'


class FeedFileName(atom.core.XmlElement):
  """sc:feed_file_name element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'feed_file_name'


class FeedType(atom.core.XmlElement):
  """sc:feed_type element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'feed_type'


class UseQuotedFields(atom.core.XmlElement):
  """sc:use_quoted_fields element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'use_quoted_fields'


class FileFormat(atom.core.XmlElement):
  """sc:file_format element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'file_format'
  use_quoted_fields = UseQuotedFields
  format = 'format'


class ProcessingStatus(atom.core.XmlElement):
  """sc:processing_status element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'processing_status'


class DatafeedEntry(gdata.data.GDEntry):
  """An entry for a Datafeed."""
  content_language = ContentLanguage
  target_country = TargetCountry
  feed_file_name = FeedFileName
  file_format = FileFormat
  attribute_language = AttributeLanguage
  processing_status = ProcessingStatus
  edited = Edited
  feed_type = FeedType


class DatafeedFeed(gdata.data.GDFeed):
  """A datafeed feed."""
  entry = [DatafeedEntry]
  total_results = TotalResults
  items_per_page = ItemsPerPage
  start_index = StartIndex


class AdultContent(atom.core.XmlElement):
  """sc:adult_content element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'adult_content'


class InternalId(atom.core.XmlElement):
  """sc:internal_id element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'internal_id'


class ReviewsUrl(atom.core.XmlElement):
  """sc:reviews_url element
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'reviews_url'


class AdwordsAccount(atom.core.XmlElement):
  """sc:adwords_account element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'adwords_account'
  status = 'status'


class AdwordsAccounts(atom.core.XmlElement):
  """sc:adwords_accounts element

  Container element for adwords accounts settings.
  """
  _qname = SC_NAMESPACE_TEMPLATE % 'adwords_accounts'
  adwords_account = [AdwordsAccount]


class ClientAccount(gdata.data.GDEntry):
  """A multiclient account entry."""
  adult_content = AdultContent
  internal_id = InternalId
  reviews_url = ReviewsUrl
  adwords_accounts = AdwordsAccounts


class ClientAccountFeed(gdata.data.GDFeed):
  """A multiclient account feed."""
  entry = [ClientAccount]
  total_results = TotalResults
  items_per_page = ItemsPerPage
  start_index = StartIndex


# Users Feed Classes
class Admin(atom.core.XmlElement):
  """sc:admin element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'admin'


class Permission(atom.core.XmlElement):
  """sc:permission element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'permission'
  scope = 'scope'


class UsersEntry(gdata.data.GDEntry):
  """A User Management Feed entry."""
  admin = Admin
  permission = [Permission]


class UsersFeed(gdata.data.GDFeed):
  """A User Management Feed."""
  entry = [UsersEntry]
  total_results = TotalResults
  items_per_page = ItemsPerPage
  start_index = StartIndex


# Data Quality Feed Classes
class ExampleItemLink(atom.core.XmlElement):
  """sc:link element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'link'


class ExampleItemTitle(atom.core.XmlElement):
  """sc:title element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'title'


class ItemId(atom.core.XmlElement):
  """sc:item_id element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'item_id'


class SubmittedValue(atom.core.XmlElement):
  """sc:submitted_value element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'submitted_value'


class ValueOnLandingPage(atom.core.XmlElement):
  """sc:value_on_landing_page element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'value_on_landing_page'


class ExampleItem(atom.core.XmlElement):
  """sc:example_item element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'example_item'
  item_id = ItemId
  link = ExampleItemLink
  title = ExampleItemTitle
  submitted_value = SubmittedValue
  value_on_landing_page = ValueOnLandingPage


class Issue(atom.core.XmlElement):
  """sc:issue element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'issue'
  id = 'id'
  last_checked = 'last_checked'
  num_items = 'num_items'
  offending_term = 'offending_term'
  example_item = [ExampleItem]


class IssueGroup(atom.core.XmlElement):
  """sc:issue_group element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'issue_group'
  issue = [Issue]
  country = 'country'
  id = 'id'


class IssueGroups(atom.core.XmlElement):
  """sc:issue_groups element"""
  _qname = SC_NAMESPACE_TEMPLATE % 'issue_groups'
  issue_group = [IssueGroup]


class DataQualityEntry(gdata.data.GDEntry):
  """A Data Quality Feed entry."""
  issue_groups = IssueGroups


class DataQualityFeed(gdata.data.GDFeed):
  """A Data Quality Feed."""
  entry = [DataQualityEntry]
  total_results = TotalResults
  items_per_page = ItemsPerPage
  start_index = StartIndex


# Local Products Feed Classes
class SalePrice(atom.core.XmlElement):
  """scp:sale_price element

  The sale price of the product. The unit attribute must be set, and should
  represent the currency.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'sale_price'
  unit = 'unit'


class SalePriceEffectiveDate(atom.core.XmlElement):
  """scp:sale_price_effective_date element

  The effective date of the sale price of the product.
  """
  _qname = SCP_NAMESPACE_TEMPLATE % 'sale_price_effective_date'


class InventoryEntry(gdata.data.BatchEntry):
  """Product entry containing local product information.

  The elements of this entry that are used are made up of five different
  namespaces. They are:

  atom: - Atom
  app: - Atom Publishing Protocol
  gd: - Google Data API
  sc: - Content API for Shopping, general attributes
  scp: - Content API for Shopping, product attributes

  Only the sc and scp namespace elements are defined here, but additional useful
  elements are defined in superclasses.

  To remove the value of an attribute of a local product, set the value of the
  attribute to the empty string. For example:

      entry.availability = Availability('')

  The following attributes are encoded as XML elements in the Atom (atom:)
  namespace: title, link, entry, id, category, content, author, created
  updated. Among these, the title, content and link tags are part of the
  required Content for Shopping API so we document them here.

  .. attribute:: availability

    The avilability of a local product.

    This should be an :class:`Availability` instance, for example::

      entry = InventoryEntry()
      entry.availability = Availability('in stock')

  .. attribute:: price

    The price for this local product.

    This should be a :class:`Price` element, including a unit argument to
    indicate the currency, for example::

      entry = InventoryEntry()
      entry.price = Price('20.00', unit='USD')

  .. attribute:: quantity

    The quantity of local product available in stock.

    This should be a :class:`Quantity` element, for example::

      entry = InventoryEntry()
      entry.quantity = Quantity('100')

  .. attribute:: sale_price

    The sale price for this local product.

    This should be a :class:`SalePrice` element, including a unit argument to
    indicate the currency, for example::

      entry = InventoryEntry()
      entry.sale_price = SalePrice('20.00', unit='USD')

  .. attribute:: sale_price_effective_date

    The effective data of the sale price for this local product.

    This should be a :class:`SalePriceEffectiveDate` element, for example::

      entry = InventoryEntry()
      entry.sale_price_effective_date = SalePriceEffectiveDate(
          '2012-01-09 2012-01-13')
  """
  availability = Availability
  price = Price
  quantity = Quantity
  sale_price = SalePrice
  sale_price_effective_date = SalePriceEffectiveDate


class InventoryFeed(gdata.data.BatchFeed):
  """Represents a feed of a merchant's local products."""
  entry = [InventoryEntry]
  total_results = TotalResults
  items_per_page = ItemsPerPage
  start_index = StartIndex
