# Feb 2006: A RELAX NG Compact schema for gnucash "v2" XML files.
# Copyright (C) 2006 Joshua Sled <jsled@asynchronous.org>

# This schema is non-normative; files emitted by gnucash <= 2.0 (and perhaps
# later) that do not validate against it likely represent a problem with the
# schema, not the file.

# http://relaxng.org/compact-tutorial-20030326.html

namespace gnc = "http://www.gnucash.org/XML/gnc"
namespace act = "http://www.gnucash.org/XML/act"
namespace book = "http://www.gnucash.org/XML/book"
namespace cd = "http://www.gnucash.org/XML/cd"
namespace cmdty = "http://www.gnucash.org/XML/cmdty"
namespace price = "http://www.gnucash.org/XML/price"
namespace slot = "http://www.gnucash.org/XML/slot"
namespace split = "http://www.gnucash.org/XML/split"
namespace sx = "http://www.gnucash.org/XML/sx"
namespace trn = "http://www.gnucash.org/XML/trn"
namespace ts = "http://www.gnucash.org/XML/ts"
namespace cust = "http://www.gnucash.org/XML/cust"
namespace job = "http://www.gnucash.org/XML/job"
namespace addr = "http://www.gnucash.org/XML/addr"
namespace owner = "http://www.gnucash.org/XML/owner"
namespace taxtable = "http://www.gnucash.org/XML/taxtable"
namespace tte = "http://www.gnucash.org/XML/tte"
namespace employee = "http://www.gnucash.org/XML/employee"
namespace order = "http://www.gnucash.org/XML/order"
namespace billterm = "http://www.gnucash.org/XML/billterm"
namespace bt-days = "http://www.gnucash.org/XML/bt-days"
namespace bt-prox = "http://www.gnucash.org/XML/bt-prox"
namespace invoice = "http://www.gnucash.org/XML/invoice"
namespace entry = "http://www.gnucash.org/XML/entry"
namespace vendor = "http://www.gnucash.org/XML/vendor"
namespace lot = "http://www.gnucash.org/XML/lot"
namespace fs = "http://www.gnucash.org/XML/fs"
namespace bgt = "http://www.gnucash.org/XML/bgt"
namespace recurrence = "http://www.gnucash.org/XML/recurrence"

start = GnuCashXml

GnuCashXml = element gnc-v2 {
  element gnc:count-data { attribute cd:type { "book" }, xsd:int },
  Book
}

Book = element gnc:book {
  attribute version { "2.0.0" },
  element book:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element book:slots { KvpSlot+ }?,
  element gnc:count-data { attribute cd:type { "commodity" }, xsd:int }?,
  element gnc:count-data { attribute cd:type { "account" }, xsd:int },
  element gnc:count-data { attribute cd:type { "transaction" }, xsd:int },
  element gnc:count-data { attribute cd:type { "schedxaction" }, xsd:int }?,
  element gnc:count-data { attribute cd:type { "budget" }, xsd:int }?,
  element gnc:count-data { attribute cd:type { "gnc:GncCustomer" }, xsd:int }?,
  element gnc:count-data { attribute cd:type { "gnc:GncEmployee" }, xsd:int }?,
  element gnc:count-data { attribute cd:type { "gnc:GncBillTerm" }, xsd:int }?,
  element gnc:count-data { attribute cd:type { "gnc:GncInvoice" }, xsd:int }?,
  element gnc:count-data { attribute cd:type { "gnc:GncEntry" }, xsd:int }?,
  Commodity*,
  PriceDb?,
  Account*,
  Transaction*,
  Customer*,
  Employee*,
  BillTerm*,
  Invoice*,
  Entry*,
  TemplateTransactions*,
  ScheduledTransaction*,
  Budget*
}

Commodity = element gnc:commodity {
  attribute version { "2.0.0" },
  element cmdty:space { text },
  element cmdty:id { text },
  element cmdty:name { text },
  element cmdty:fraction { text }
}

PriceDb = element gnc:pricedb {
  attribute version { "1" },
  Price+
}

Price = element price {
  element price:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element price:commodity {
    element cmdty:space { text },
    element cmdty:id { text }
  },
  element price:currency {
    element cmdty:space { text },
    element cmdty:id { text }
  },
  element price:time { TimeStamp },
  element price:source { text },
  # dialog-price-editor.c:type_index_to_string...
  element price:type { "bid" | "ask" | "last" | "nav" | "unknown" },
  element price:value { text }
}

TimeStamp = element ts:date { xsd:string { pattern = "[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} -[0-9]{4}" } }

Account = element gnc:account {
  attribute version { "2.0.0" },
  element act:name { text },
  element act:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element act:type { "NO_TYPE"
                   | "BANK"
                   | "CASH"
                   | "CREDIT"
                   | "ASSET"
                   | "LIABILITY"
                   | "STOCK"
                   | "MUTUAL"
                   | "CURRENCY"
                   | "INCOME"
                   | "EXPENSE"
                   | "EQUITY"
                   | "RECEIVABLE"
                   | "PAYABLE"
                   | "CHECKING"
                   | "SAVINGS"
                   | "MONEYMRKT"
                   | "CREDITLINE" },
  element act:commodity { 
    element cmdty:space { text },
    element cmdty:id { text }
  },
  element act:commodity-scu { xsd:int },
  element act:code { text }?,
  element act:non-standard-scu {empty}?,
  element act:description { text }?,
  element act:slots { KvpSlot+ }?,
  element act:parent { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}?,
  element act:lots { Lot+ }?
}

Lot = element gnc:lot {
  attribute version { "2.0.0" },
  element lot:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element lot:slots { KvpSlot+ }
}

KvpSlot = element slot {
  element slot:key { text },
  ( element slot:value { attribute type { "frame" }, KvpSlot+ }
  | element slot:value { attribute type { "integer" }, xsd:int }
  | element slot:value { attribute type { "double" }, xsd:double }
  | element slot:value { attribute type { "numeric" }, xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } }
  | element slot:value { attribute type { "string" }, text }
  | element slot:value { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}
  | element slot:value { attribute type { "timespec" }, TimeStamp }
  | element slot:value { attribute type { "binary" }, xsd:string { pattern = "[0-9a-f]*" }}
  )
}

Transaction = element gnc:transaction {
  attribute version { "2.0.0" },
  element trn:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element trn:currency {
    element cmdty:space { text },
    element cmdty:id { text }
  }?,
  element trn:num { text }?,
  element trn:date-posted { TimeStamp },
  element trn:date-entered { TimeStamp, element ts:ns { xsd:int }? },
  element trn:description { text },
  element trn:slots { KvpSlot+ }?,
  element trn:splits { Split+ }
}

Split = element trn:split {
  element split:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element split:memo { text }?,
  # this is actually pretty constrained, but...
  element split:action { text }?,
  element split:reconciled-state { "y" | "n" | "c" },
  element split:reconcile-date { TimeStamp }?,
  element split:value { text }?,
  element split:quantity { text },
  element split:account { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element split:slots { KvpSlot+ }?,
  element split:lot { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}?
}

Customer = element gnc:GncCustomer {
  attribute version { "2.0.0" },
  element cust:guid { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element cust:name { text },
  element cust:id { text },
  element cust:addr {
    attribute version { "2.0.0" },
    element addr:name { text }?,
    element addr:addr1 { text }?,
    element addr:addr2 { text }?,
    element addr:addr3 { text }?,
    element addr:phone { text }?,
    element addr:fax { text }?,
    element addr:email { text }?
  },
  element cust:shipaddr {
    attribute version { "2.0.0" },
    ( empty
    | ( element addr:name { text }?,
        element addr:addr1 { text }?,
        element addr:addr2 { text }?,
        element addr:addr3 { text }?,
        element addr:phone { text }?,
        element addr:fax { text }?,
        element addr:email { text }?
      )
    )
  },
  element cust:terms { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}?,
  # gncTaxTable.c:gncTaxIncludedTypeToString...
  element cust:taxincluded { "YES" | "NO" | "USEGLOBAL"},
  element cust:active { xsd:boolean { pattern = "[01]" } },
  element cust:discount { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } },
  element cust:credit { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } },
  element cust:currency {
    element cmdty:space { text },
    element cmdty:id { text }
  },
  element cust:use-tt { xsd:boolean { pattern = "[01]" } }
}

Employee = element gnc:GncEmployee {
  attribute version { "2.0.0" },
  element employee:guid { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element employee:username { text },
  element employee:id { text },
  element employee:addr {
    attribute version { "2.0.0" },
    element addr:name { text }?,
    element addr:addr1 { text },
    element addr:addr2 { text }?,
    element addr:addr3 { text }?,
    element addr:phone { text }?,
    element addr:fax { text }?,
    element addr:email { text }?
  },
  element employee:active { xsd:boolean { pattern = "[01]" } },
  element employee:workday { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } },
  element employee:rate { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } },
  element employee:currency {
    element cmdty:space { text },
    element cmdty:id { text }
  },
  element employee:ccard { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}
}

BillTerm = element gnc:GncBillTerm {
  attribute version { "2.0.0" },
  element billterm:guid { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element billterm:name { text },
  element billterm:desc { text },
  element billterm:refcount { xsd:int },
  element billterm:invisible { xsd:boolean { pattern = "[01]" } },
  element billterm:child { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}?,
  element billterm:parent { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}?,
  element billterm:days {
    element bt-days:due-days { xsd:int }
  }
}

Invoice = element gnc:GncInvoice {
  attribute version { "2.0.0" },
  element invoice:guid { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element invoice:id { text }, # 0-padded xsd:int
  element invoice:owner {
    attribute version { "2.0.0" },
    element owner:type { text }, # constrained?
    element owner:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}
  },
  element invoice:opened { TimeStamp },
  element invoice:posted { TimeStamp },
  element invoice:terms { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}?,
  element invoice:billing_id { text }?,
  element invoice:notes { text }?,
  element invoice:active { xsd:boolean { pattern = "[01]" } },
  element invoice:posttxn { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element invoice:postlot { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element invoice:postacc { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element invoice:currency {
    element cmdty:space { text },
    element cmdty:id { text }
  },
  element invoice:billto {
    attribute version { "2.0.0" },
    element owner:type { "gncCustomer" },
    element owner:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }}
  }?,
  element invoice:charge-amt { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } }?
}

Entry = element gnc:GncEntry {
  attribute version { "2.0.0" },
  element entry:guid { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element entry:date { TimeStamp },
  element entry:entered { TimeStamp },
  element entry:description { text },
  element entry:action { text }?, # probably constrained
  element entry:qty { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } },

  ( element entry:i-acct { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
    element entry:i-price { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" } },
    element entry:invoice { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
    # gncTaxTable.c:gncAmountTypeToString...
    element entry:i-disc-type { "VALUE" | "PERCENT" },
    # gncEntry.c:gncEntryDiscountHowToString...
    element entry:i-disc-how { "PRETAX" | "POSTTAX" | "SAMETIME" },
    element entry:i-taxable { xsd:boolean { pattern = "[01]" } },
    element entry:i-taxincluded { xsd:boolean { pattern = "[01]" } }
  )?,
  ( element entry:b-acct { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
    element entry:b-price { xsd:string { pattern = "-?[0-9]+/-?[0-9]+" }},
    element entry:bill { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
    element entry:billable { xsd:boolean { pattern = "[01]" } },
    element entry:b-taxable { xsd:boolean { pattern = "[01]" } },
    element entry:b-taxincluded { xsd:boolean { pattern = "[01]" } },
    # gncEntry.c:gncEntryPaymentTypeToString...
    element entry:b-pay { "CASH" | "CARD" }
  )?
}

TemplateTransactions = element gnc:template-transactions {
  Account+,
  Transaction+
}

ScheduledTransaction = element gnc:schedxaction {
  attribute version { "1.0.0" },
  element sx:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element sx:name { text },
  element sx:enabled { "y" | "n" },
  element sx:autoCreate { "y" | "n" },
  element sx:autoCreateNotify { "y" | "n" },
  element sx:advanceCreateDays { xsd:int },
  element sx:advanceRemindDays { xsd:int },
  element sx:instanceCount { xsd:int },
  element sx:start { GDate },
  element sx:templ-acct { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element sx:freqspec { GncFreqSpec }  
}

GDate = element gdate { xsd:string { pattern = "[0-9]{4}-[0-9]{2}-[0-9]{2}" } }

GncFreqSpec = element gnc:freqspec {
  attribute version { "1.0.0" },
  element fs:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  # gnc-freqspec-xml-v2.c:gnc_freqSpec_dom_tree_create
  element fs:ui_type { "none"
                     | "once"
                     | "daily"
                     | "daily_mf"
                     | "weekly"
                     | "bi_weekly"
                     | "semi_monthly"
                     | "monthly"
                     | "quarterly"
                     | "tri_anually"
                     | "semi_yearly"
                     | "yearly" },
  ( element fs:none { empty }
  | element fs:once { element fs:date { GDate } }
  | element fs:daily {
      element fs:interval { xsd:int },
      element fs:offset { xsd:int }
    }
  | element fs:weekly {
      element fs:interval { xsd:int },
      element fs:offset { xsd:int }
    }
  | element fs:monthly {
      element fs:interval { xsd:int },
      element fs:offset { xsd:int },
      element fs:day { xsd:int }
    }
  | element fs:month_relative {
      element fs:interval { xsd:int },
      element fs:offset { xsd:int },
      element fs:weekday { xsd:int },
      element fs:occurrence { xsd:int }
    }
  | element fs:composite { GncFreqSpec+ }
  )
}

Budget = element gnc:budget {
  attribute version { "2.0.0" },
  element bgt:id { attribute type { "guid" }, xsd:string { pattern = "[0-9a-f]{32}" }},
  element bgt:name { text },
  element bgt:description { text },
  element bgt:num-periods { xsd:int },
  element bgt:recurrence {
    attribute version { "1.0.0" },
    element recurrence:mult { xsd:int },
    # Recurrence.c:period_type_strings...
    element recurrence:period_type { "once" | "day" | "week" | "month" | "end of month" | "nth weekday" | "last weekday" | "year" },
    element recurrence:start { GDate }
  },
  element bgt:slots { KvpSlot+ }
}
