# -*- rnc -*-
# RELAX NG Compact Syntax Grammar for the
# Atom Format Specification Version 11

namespace atom = "http://www.w3.org/2005/Atom"
namespace xhtml = "http://www.w3.org/1999/xhtml"
namespace s = "http://www.ascc.net/xml/schematron"
namespace local = ""

start = atomFeed | atomEntry

# Common attributes

atomCommonAttributes =
  attribute xml:base { atomUri }?,
  attribute xml:lang { atomLanguageTag }?,
  undefinedAttribute*

# Text Constructs

atomPlainTextConstruct =
  atomCommonAttributes,
  attribute type { "text" | "html" }?,
  text

atomXHTMLTextConstruct =
  atomCommonAttributes,
  attribute type { "xhtml" },
  xhtmlDiv

atomTextConstruct = atomPlainTextConstruct | atomXHTMLTextConstruct

# Person Construct

atomPersonConstruct =
  atomCommonAttributes,
  (element atom:name { text }
    & element atom:uri { atomUri }?
    & element atom:email { atomEmailAddress }?
    & extensionElement*)

# Date Construct

atomDateConstruct =
  atomCommonAttributes,
  xsd:dateTime

# atom:feed

atomFeed =
  [
      s:rule [
        context = "atom:feed"
        s:assert [
            test = "atom:author or not(atom:entry[not(atom:author)])"
            "An atom:feed must have an atom:author unless all "
            ~ "of its atom:entry children have an atom:author."
        ]
      ]
  ]
  element atom:feed {
      atomCommonAttributes,
      (atomAuthor*
      & atomCategory*
      & atomContributor*
      & atomGenerator?
      & atomIcon?
      & atomId
      & atomLink*
      & atomLogo?
      & atomRights?
      & atomSubtitle?
      & atomTitle
      & atomUpdated
      & extensionElement*),
      atomEntry*
  }

# atom:entry

atomEntry =
  [
      s:rule [
        context = "atom:entry"
        s:assert [
            test = "atom:link[@rel='alternate'] "
            ~ "or atom:link[not(@rel)] "
            ~ "or atom:content"
            "An atom:entry must have at least one atom:link element "
            ~ "with a rel attribute of 'alternate' "
            ~ "or an atom:content."
        ]
      ]
      s:rule [
        context = "atom:entry"
        s:assert [
            test = "atom:author or "
            ~ "../atom:author or atom:source/atom:author"
            "An atom:entry must have an atom:author "
            ~ "if its feed does not."
        ]
      ]
  ]
  element atom:entry {
      atomCommonAttributes,
      (atomAuthor*
      & atomCategory*
      & atomContent?
      & atomContributor*
      & atomId
      & atomLink*
      & atomPublished?
      & atomRights?
      & atomSource?
      & atomSummary?
      & atomTitle
      & atomUpdated
      & extensionElement*)
  }

# atom:content

atomInlineTextContent =
  element atom:content {
      atomCommonAttributes,
      attribute type { "text" | "html" }?,
      (text)*
  }

atomInlineXHTMLContent =
  element atom:content {
      atomCommonAttributes,
      attribute type { "xhtml" },
      xhtmlDiv
  }

atomInlineOtherContent =
  element atom:content {
      atomCommonAttributes,
      attribute type { atomMediaType }?,
      (text|anyElement)*
  }

atomOutOfLineContent =
  element atom:content {
      atomCommonAttributes,
      attribute type { atomMediaType }?,
      attribute src { atomUri },
      empty
  }

atomContent = atomInlineTextContent
| atomInlineXHTMLContent
| atomInlineOtherContent
| atomOutOfLineContent

# atom:author

atomAuthor = element atom:author { atomPersonConstruct }

# atom:category

atomCategory =
  element atom:category {
      atomCommonAttributes,
      attribute term { text },
      attribute scheme { atomUri }?,
      attribute label { text }?,
      undefinedContent
  }

# atom:contributor

atomContributor = element atom:contributor { atomPersonConstruct }

# atom:generator

atomGenerator = element atom:generator {
  atomCommonAttributes,
  attribute uri { atomUri }?,
  attribute version { text }?,
  text
}

# atom:icon

atomIcon = element atom:icon {
  atomCommonAttributes,
  (atomUri)
}

# atom:id

atomId = element atom:id {
  atomCommonAttributes,
  (atomUri)
}

# atom:logo

atomLogo = element atom:logo {
  atomCommonAttributes,
  (atomUri)
}

# atom:link

atomLink =
  element atom:link {
      atomCommonAttributes,
      attribute href { atomUri },
      attribute rel { atomNCName | atomUri }?,
      attribute type { atomMediaType }?,
      attribute hreflang { atomLanguageTag }?,
      attribute title { text }?,
      attribute length { text }?,
      undefinedContent
  }

# atom:published

atomPublished = element atom:published { atomDateConstruct }

# atom:rights

atomRights = element atom:rights { atomTextConstruct }

# atom:source

atomSource =
  element atom:source {
      atomCommonAttributes,
      (atomAuthor*
      & atomCategory*
      & atomContributor*
      & atomGenerator?
      & atomIcon?
      & atomId?
      & atomLink*
      & atomLogo?
      & atomRights?
      & atomSubtitle?
      & atomTitle?
      & atomUpdated?
      & extensionElement*)
  }

# atom:subtitle

atomSubtitle = element atom:subtitle { atomTextConstruct }

# atom:summary

atomSummary = element atom:summary { atomTextConstruct }

# atom:title

atomTitle = element atom:title { atomTextConstruct }

# atom:updated

atomUpdated = element atom:updated { atomDateConstruct }

# Low-level simple types

atomNCName = xsd:string { minLength = "1" pattern = "[^:]*" }

# Whatever a media type is, it contains at least one slash
atomMediaType = xsd:string { pattern = ".+/.+" }

# As defined in RFC 3066
atomLanguageTag = xsd:string {
  pattern = "[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*"
}

# Unconstrained; it's not entirely clear how IRI fit into
# xsd:anyURI so let's not try to constrain it here
atomUri = text

# Whatever an email address is, it contains at least one @
atomEmailAddress = xsd:string { pattern = ".+@.+" }

# Simple Extension

simpleExtensionElement =
  element * - atom:* {
      text
  }

# Structured Extension

structuredExtensionElement =
  element * - atom:* {
      (attribute * { text }+,
        (text|anyElement)*)
    | (attribute * { text }*,
      (text?, anyElement+, (text|anyElement)*))
  }

# Other Extensibility

extensionElement =
  simpleExtensionElement | structuredExtensionElement

undefinedAttribute =
  attribute * - (xml:base | xml:lang | local:*) { text }

undefinedContent = (text|anyForeignElement)*

anyElement =
  element * {
      (attribute * { text }
      | text
      | anyElement)*
  }

anyForeignElement =
  element * - atom:* {
      (attribute * { text }
      | text
      | anyElement)*
  }

# XHTML

anyXHTML = element xhtml:* {
  (attribute * { text }
    | text
    | anyXHTML)*
}

xhtmlDiv = element xhtml:div {
  (attribute * { text }
    | text
    | anyXHTML)*
}

# EOF
