api/xml/xml.go
package xml
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"path"
"reflect"
"regexp"
"strings"
"github.com/benbjohnson/clock"
"github.com/pkg/errors"
"github.com/18F/e-QIP-prototype/api"
)
// Service is an implementation of handling XML.
type Service struct {
clock clock.Clock
templatePath string
}
// NewXMLService returns a new XML service
func NewXMLService(templatePath string) Service {
localClock := clock.New()
return Service{
clock: localClock,
templatePath: templatePath,
}
}
// NewXMLServiceWithMockClock allows you to create an XML service with a minuplable clock
func NewXMLServiceWithMockClock(templatePath string, clock clock.Clock) Service {
return Service{
clock,
templatePath,
}
}
// applicationPackager is created for each call to PackageXML. It maintains some state
// required for packaging the entire applicaiton
type applicationPackager struct {
xmlService Service
appFormType string
appFormVersion string
}
func newApplicationPackager(xmlService Service, application api.Application) applicationPackager {
return applicationPackager{
xmlService: xmlService,
appFormType: application.FormType(),
appFormVersion: application.FormVersion(),
}
}
// PackageXML returns the XML representation of an application
func (service Service) PackageXML(app api.Application) (template.HTML, error) {
// This is perhaps silly. I think that things might work with just the raw application sections
// but I think that rather than expose that it will be better to keep the JSON as the interface
jsonBytes, jsonErr := json.Marshal(app)
if jsonErr != nil {
return template.HTML(""), errors.Wrap(jsonErr, "Unable to marshal application")
}
var data map[string]interface{}
unmarhalErr := json.Unmarshal(jsonBytes, &data)
if unmarhalErr != nil {
return template.HTML(""), errors.Wrap(jsonErr, "Unable to re-un-marshal application")
}
packager := newApplicationPackager(service, app)
return packager.defaultTemplate("application.xml", data)
}
// defaultTemplate returns a template given data.
func (p applicationPackager) defaultTemplate(templateName string, data map[string]interface{}) (template.HTML, error) {
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs(templateName, data, fmap)
}
func (p applicationPackager) templateFMap() template.FuncMap {
// fmap is a mapping of functions to be used within the XML template execution.
// These can be helper functions for formatting or even to process complex structure
// types.
fmap := template.FuncMap{
"address": p.address,
"addressIn": addressIn,
"agencyType": agencyType,
"apoFpo": p.apoFpo,
"branch": branch,
"branchToBool": branchToBool,
"branchcollectionHas": branchcollectionHas,
"branchAny": branchAny,
"checkbox": checkbox,
"checkboxHas": checkboxHas,
"checkboxTrueFalse": checkboxTrueFalse,
"citizenshipStatus": citizenshipStatus,
"country": p.countryValue,
"countryComments": countryComments,
"citizenshipHas": citizenshipHas,
"clearanceType": clearanceType,
"date": p.date,
"dateOptional": p.dateOptional,
"dateEstimated": dateEstimated,
"daterange": p.daterange,
"daysInRange": daysInRange,
"deceased": deceased,
"degreeType": degreeType,
"derivedBasis": derivedBasis,
"naturalizedBasis": naturalizedBasis,
"diagnosisType": diagnosisType,
"dischargeType": dischargeType,
"doctorFirstName": doctorFirstName,
"doctorLastName": doctorLastName,
"drugType": drugType,
"spouseForeignDocType": spouseForeignDocType,
"foreignAffiliation": foreignAffiliation,
"frequencyType": frequencyType,
"email": email,
"emailOptional": p.emailOptional,
"employmentType": employmentType,
"hairType": hairType,
"hasRelativeType": hasRelativeType,
"inc": inc,
"formType": p.formType,
"isPostOffice": isPostOffice,
"isDomestic": isDomestic,
"isInternational": isInternational,
"location": p.location,
"locationOverrideLayout": p.locationOverrideLayout,
"militaryAddress": p.militaryAddress,
"militaryStatus": militaryStatus,
"monthYear": p.monthYear,
"monthYearOptional": p.monthYearOptional,
"monthYearDaterange": p.monthYearDaterange,
"name": p.name,
"nameLastFirst": p.nameLastFirst,
"notApplicable": notApplicable,
"now": p.now,
"number": number,
"padDigits": padDigits,
"radio": radio,
"schoolType": schoolType,
"selectBenefit": selectBenefit,
"selfAbroadDocType": selfAbroadDocType,
"selfForeignDocType": selfForeignDocType,
"severanceType": severanceType,
"street": street,
"suffixType": suffixType,
"relationshipType": relationshipType,
"relativeForeignDocType": relativeForeignDocType,
"telephone": p.telephone,
"telephoneNoNumber": telephoneNoNumber,
"telephoneNoTimeOfDay": p.telephoneNoTimeOfDay,
"text": text,
"textarea": textarea,
"toUpper": toUpper,
"treatment": p.treatment,
"treatmentAnswerType": treatmentAnswerType,
"tmpl": p.defaultTemplate,
}
return fmap
}
// XXX
// Work-around for sloppy template logic where empty elements
// are generated in scenarios where no element is applicable.
// For example, UnemployedComment when not unemployed. See:
// https://github.com/18F/e-QIP-prototype/issues/717
func applyBulkFixes(xml string) string {
replaceWithEmpty := []string{
"<[a-zA-Z_0-9]+></[a-zA-Z_0-9]+>", // empty elements
" DoNotKnow=\"False\"",
" DoNotKnow=\"\"",
" Type=\"\"",
" Estimated=\"\"",
" Estimated=\"false\"",
}
x := xml
for _, n := range replaceWithEmpty {
re := regexp.MustCompile(n)
x = re.ReplaceAllString(x, "")
}
return x
}
func (p applicationPackager) xmlTemplate(name string, data map[string]interface{}) (template.HTML, error) {
return p.xmlTemplateWithFuncs(name, data, template.FuncMap{})
}
// xmlTemplateWithFuncs executes an XML template with mapped functions to be used with the
// given entity.
func (p applicationPackager) xmlTemplateWithFuncs(name string, data map[string]interface{}, fmap template.FuncMap) (template.HTML, error) {
path := path.Join(p.xmlService.templatePath, name)
tmpl := template.Must(template.New(name).Funcs(fmap).ParseFiles(path))
var output bytes.Buffer
if err := tmpl.Execute(&output, data); err != nil {
return template.HTML(""), err
}
return template.HTML(applyBulkFixes(output.String())), nil
}
func getInterfaceAsBytes(anon interface{}) []byte {
js, err := json.Marshal(anon)
if err != nil {
return nil
}
return js
}
// now returns the server's local time in yyyy-MM-dd
func (p applicationPackager) now() string {
t := p.xmlService.clock.Now()
return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day())
}
// formType returns true if the supplied string matches the current application
// format of formList is comma separated form identifiers. You can include either just
// the FormType or `FormType.FormVersion`. It would be reasonable to add excludes
// in the future as well, but that's left as an exercise to the reader
// example: {{if formType "SF86,SF85.2017-12-draft7"}}
// example: {{if formType "SF86,SF85P"}}
func (p applicationPackager) formType(formList string) bool {
formTypes := strings.Split(formList, ",")
for _, formT := range formTypes {
if formT == p.appFormType {
return true
}
if formT == fmt.Sprintf("%s.%s", p.appFormType, p.appFormVersion) {
return true
}
}
return false
}
func apoFpoView(primary map[string]interface{}, altAddress map[string]interface{}) map[string]interface{} {
view := make(map[string]interface{})
view["Primary"] = primary
if props, ok := altAddress["props"]; ok {
if hasApo, ok := (props.(map[string]interface{}))["HasDifferentAddress"]; ok {
if alt, ok := (props.(map[string]interface{}))["Address"]; ok {
view["HasApo"] = hasApo
view["Alternate"] = alt
return view
}
}
}
view["HasApo"] = make(map[string]interface{})
view["Alternate"] = make(map[string]interface{})
return view
}
// apoFpo generates the APOFPO element, given a primary address and an alternate address.
// primary is a required location type. altAddress is an optionally populated physicaladdress type.
func (p applicationPackager) apoFpo(primary map[string]interface{}, altAddress map[string]interface{}) (template.HTML, error) {
// Templates operate on a single dot set, so create a single view on the data
view := apoFpoView(primary, altAddress)
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("apofpo.xml", view, fmap)
}
// selectBenefit returns the appropriate sub-tree from Foreign.BenefitActivity.props.List.props.items[*],
// given the benefit frequency type. This faciltates less duplication in foreign-financial-benefits.xml.
func selectBenefit(freqType string, benefitItem map[string]interface{}) (map[string]interface{}, error) {
selector := freqType + "Benefit"
if t, ok := benefitItem[selector]; ok {
if p, ok := (t.(map[string]interface{}))["props"]; ok {
return (p.(map[string]interface{})), nil
}
}
return nil, errors.New(selector + " not found in benefit item")
}
func (p applicationPackager) address(loc map[string]interface{}, dnk map[string]interface{}) (template.HTML, error) {
view := make(map[string]interface{})
view["Location"] = loc
view["DoNotKnow"] = dnk
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("address.xml", view, fmap)
}
// Put simple structures here where they only output a string
// simpleValue returns the value property of a basic payload type.
func simpleValue(data map[string]interface{}) string {
props, ok := data["props"]
if ok {
return (props.(map[string]interface{}))["value"].(string)
}
return ""
}
// XXX
// Temporary measure to work-around issue where eApp has one field
// where user is prompted to enter as "Last name, First name" but
// e-QIP has two separate XML elements.
// See: https://github.com/18F/e-QIP-prototype/issues/715
func doctorLastName(data map[string]interface{}) string {
fullName := simpleValue(data)
if fullName == "" {
// Shouldn't get here as UI should enforce non-empty field
return ""
}
commaDelim := strings.SplitN(fullName, ",", 2)
if len(commaDelim) == 2 {
return strings.TrimSpace(commaDelim[0])
}
return fullName
}
// XXX
// See comment on doctorLastName()
func doctorFirstName(data map[string]interface{}) string {
fullName := simpleValue(data)
if fullName == "" {
// Shouldn't get here as UI should enforce non-empty field
return ""
}
commaDelim := strings.SplitN(fullName, ",", 2)
if len(commaDelim) == 2 {
return strings.TrimSpace(commaDelim[1])
}
return ""
}
func branch(data map[string]interface{}) string {
return simpleValue(data)
}
func branchAny(branches ...map[string]interface{}) string {
for _, branch := range branches {
if simpleValue(branch) == "Yes" {
return "Yes"
}
}
return "No"
}
func branchcollectionHas(data map[string]interface{}) string {
props, ok := data["props"]
if !ok {
return "No"
}
items, ok := (props.(map[string]interface{}))["items"].([]interface{})
if !ok {
return "No"
}
if len(items) == 0 {
return "No"
}
for _, item := range items {
bi := item.(map[string]interface{})["Item"]
b, ok := (bi.(map[string]interface{}))["Has"]
if !ok {
return "No"
}
val := simpleValue(b.(map[string]interface{}))
if val == "Yes" {
return val
}
}
return "No"
}
func email(data map[string]interface{}) string {
return simpleValue(data)
}
func (p applicationPackager) emailOptional(e, dnk map[string]interface{}) (template.HTML, error) {
view := make(map[string]interface{})
view["Email"] = e
view["DoNotKnow"] = dnk
fmap := template.FuncMap{
"notApplicable": notApplicable,
"email": email,
}
return p.xmlTemplateWithFuncs("email-optional.xml", view, fmap)
}
func text(data map[string]interface{}) string {
return simpleValue(data)
}
func textarea(data map[string]interface{}) string {
return simpleValue(data)
}
func number(data map[string]interface{}) string {
return simpleValue(data)
}
func radio(data map[string]interface{}) string {
return simpleValue(data)
}
func checkbox(data map[string]interface{}) string {
props, ok := data["props"]
if ok {
values, ok := (props.(map[string]interface{}))["values"].([]interface{})
if !ok {
return ""
}
ss := []string{}
for _, v := range values {
ss = append(ss, v.(string))
}
return strings.Join(ss, ",")
}
return ""
}
func checkboxHas(data map[string]interface{}, target string) string {
props, ok := data["props"]
if ok {
values, ok := (props.(map[string]interface{}))["values"].([]interface{})
if !ok {
return "False"
}
for _, s := range values {
if s == target {
return "True"
}
}
}
return "False"
}
func hasRelativeType(data map[string]interface{}, target string) string {
props, ok := data["props"]
if !ok {
return "False"
}
items, ok := (props.(map[string]interface{}))["items"].([]interface{})
if !ok {
return "False"
}
if len(items) == 0 {
return "False"
}
for _, item := range items {
ci := item.(map[string]interface{})["Item"]
relation, ok := (ci.(map[string]interface{}))["Relation"]
if !ok {
return "False"
}
val := simpleValue(relation.(map[string]interface{}))
if val == target {
return "True"
}
}
return "False"
}
// relationshipType translates our enums to eqip specific enums
func relationshipType(str string) string {
types := map[string]string{
"Mother": "01Mother",
"Father": "02Father",
"Stepmother": "03Stepmother",
"Stepfather": "04Stepfather",
"Fosterparent": "05FosterParent",
"Child": "06Child",
"Stepchild": "07Stepchild",
"Brother": "08Brother",
"Sister": "09Sister",
"Stepbrother": "10Stepbrother",
"Stepsister": "11Stepsister",
"Half-brother": "12HalfBrother",
"Half-sister": "13HalfSister",
"Father-in-law": "14FatherInLaw",
"Mother-in-law": "15MotherInLaw",
"Guardian": "16Guardian",
}
return types[str]
}
func foreignAffiliation(str string) string {
types := map[string]string{
"Yes": "Yes",
"No": "No",
"I don't know": "IDontKnow",
}
return types[str]
}
func deceased(str string) string {
types := map[string]string{
"Yes": "Yes",
"No": "No",
"DK": "IDontKnow",
}
return types[str]
}
// citizenshipStatus translates our enums to eqip specific enums
func citizenshipStatus(status string) string {
alias := map[string]string{
"Citizen": "USByBirth",
"ForeignBorn": "USByBirthOutsideUS",
"Naturalized": "USNotByBirth",
"Derived": "DerivedUSCitizen",
"NotCitizen": "Alien",
}
return alias[status]
}
func schoolType(t string) string {
alias := map[string]string{
"High School": "HighSchool",
"College": "College",
"Vocational": "Vocational",
"Correspondence": "Correspondence",
}
return alias[t]
}
func degreeType(t string) string {
alias := map[string]string{
"High School Diploma": "HighSchool",
"Associate": "Associate",
"Bachelor": "Bachelor",
"Master": "Master",
"Doctorate": "Doctorate",
"Professional": "Professional",
"Other": "Other",
}
return alias[t]
}
func diagnosisType(t string) string {
alias := map[string]string{
"Psychotic disorder": "PsychoticDisorder",
"Schizophrenia": "Schizophrenia",
"Schizoaffective disorder": "SchizoaffectiveDisorder",
"Delusional disorder": "DelusionalDisorder",
"Bipolar mood disorder": "BipolarMoodDisorder",
"Borderline personality disorder": "BorderlinePersonalityDisorder",
"Antisocial personality disorder": "AntisocialPersonalityDisorder",
}
return alias[t]
}
// relativeForeignDocType translates our enums to eqip specific enums
// XXX https://github.com/18F/e-QIP-prototype/issues/680
func relativeForeignDocType(docType string) string {
alias := map[string]string{
"FS": "FS240or545",
"DS": "DS1350",
"NaturalizedAlien": "NaturalizedAlienRegistration",
"NaturalizedPermanent": "NaturalizedPermanentResident",
"NaturalizedCertificate": "NaturalizationCertificate",
"DerivedAlien": "DerivedAlienRegistration",
"DerivedPermanent": "DerivedPermanentResident",
"DerivedCertificate": "DerivedCitizenshipCertificate",
"Permanent": "NonCitizenI551",
"Employment": "NonCitizenI766",
"Arrival": "NonCitizenI94",
"Visa": "NonCitizenVisa",
"F1": "NonCitizenI20",
"J1": "NonCitizenDS2019",
"Other": "Other",
}
return alias[docType]
}
// spouseForeignDocType translates our enums to eqip specific enums
func spouseForeignDocType(docType string) string {
alias := map[string]string{
"FS240": "FS240or545",
"DS1350": "DS1350",
"AlienRegistration": "NaturalizedAlienRegistration",
"PermanentResident": "NaturalizedPermanentResident",
"CertificateOfNaturalization": "NaturalizationCertificate",
"DerivedAlienRegistration": "DerivedAlienRegistration",
"DerivedPermanentResident": "DerivedPermanentResident",
"DerivedCertificateOfNaturalization": "DerivedCitizenshipCertificate",
"I-551": "NonCitizenI551",
"I-766": "NonCitizenI766",
"I-94": "NonCitizenI94",
"Visa": "NonCitizenVisa",
"NonImmigrantStudent": "NonCitizenI20",
"ExchangeVisitor": "NonCitizenDS2019",
"Other": "Other",
}
return alias[docType]
}
func selfForeignDocType(docType string) string {
alias := map[string]string{
"I-94": "I94",
"U.S. Visa": "Visa",
"I-20": "I20",
"DS-2019": "DS2019",
"Other": "Other",
}
return alias[docType]
}
func selfAbroadDocType(docType string) string {
alias := map[string]string{
"FS-240": "FS240",
"DS-1350": "DS1350",
"FS-545": "FS545",
"Other": "Other",
}
return alias[docType]
}
// employmentType translates our enums to eqip specific enums
func employmentType(empType string) string {
alias := map[string]string{
"ActiveMilitary": "ActiveMilitaryDuty",
"NationalGuard": "NationalGuard",
"USPHS": "USPHS",
"OtherFederal": "OtherFederal",
"StateGovernment": "State",
"SelfEmployment": "SelfEmployed",
"Unemployment": "Unemployed",
"FederalContractor": "FederalContractor",
"NonGovernment": "NonGovernment",
"Other": "Other",
}
return alias[empType]
}
func militaryStatus(status string) string {
alias := map[string]string{
"ActiveDuty": "Active",
"ActiveReserve": "ActiveReserve",
"InactiveReserve": "InactiveReserve",
}
return alias[status]
}
// addressIn returns true if Location entity (Address) is in the specified country
func addressIn(location map[string]interface{}, country string) bool {
props, ok := location["props"]
if !ok {
return false
}
c, ok := (props.(map[string]interface{}))["country"]
if !ok {
return false
}
if c == country {
return true
}
return false
}
func citizenshipHas(data map[string]interface{}, country string) bool {
props, ok := data["props"]
if !ok {
return false
}
items, ok := (props.(map[string]interface{}))["value"].([]interface{})
if !ok {
return false
}
if len(items) == 0 {
return false
}
for _, item := range items {
if item == country {
return true
}
}
return false
}
func daysInRange(v string) string {
alias := map[string]string{
"1-5": "OneToFive",
"6-10": "SixToTen",
"11-20": "ElevenToTwenty",
"21-30": "TwentyoneToThirty",
"More than 30": "MoreThanThirty",
"Many short trips": "ManyShortTrips",
}
return alias[v]
}
// Put attribute helpers here
func dateEstimated(data map[string]interface{}) string {
// Deserialize the initial payload from a JSON structure
payload := &api.Payload{}
entity, err := payload.UnmarshalEntity(getInterfaceAsBytes(data))
if err != nil {
return ""
}
date := entity.(*api.DateControl)
if date.Estimated {
return "Estimated"
}
return ""
}
func notApplicable(data map[string]interface{}) string {
// Deserialize the initial payload from a JSON structure
payload := &api.Payload{}
entity, err := payload.UnmarshalEntity(getInterfaceAsBytes(data))
if err != nil {
return ""
}
na := entity.(*api.NotApplicable)
if na.Applicable {
return "False"
}
return "True"
}
func telephoneNoNumber(data map[string]interface{}) string {
// Deserialize the initial payload from a JSON structure
payload := &api.Payload{}
entity, err := payload.UnmarshalEntity(getInterfaceAsBytes(data))
if err != nil {
return ""
}
telephone := entity.(*api.Telephone)
if telephone.NoNumber {
return "True"
}
return "False"
}
func checkboxTrueFalse(data map[string]interface{}) string {
// Deserialize the initial payload from a JSON structure
payload := &api.Payload{}
entity, err := payload.UnmarshalEntity(getInterfaceAsBytes(data))
if err != nil {
return ""
}
cb := entity.(*api.Checkbox)
if cb.Checked {
return "true"
}
return "false"
}
func unmarshalLocation(data map[string]interface{}) (*api.Location, error) {
// Deserialize the initial payload from a JSON structure
payload := &api.Payload{}
entity, err := payload.UnmarshalEntity(getInterfaceAsBytes(data))
if err != nil {
return nil, err
}
location := entity.(*api.Location)
return location, nil
}
func isDomestic(data map[string]interface{}) (bool, error) {
location, err := unmarshalLocation(data)
if err != nil {
return false, err
}
return location.IsDomestic(), nil
}
func isInternational(data map[string]interface{}) (bool, error) {
location, err := unmarshalLocation(data)
if err != nil {
return false, err
}
return location.IsInternational(), nil
}
func isPostOffice(data map[string]interface{}) (bool, error) {
location, err := unmarshalLocation(data)
if err != nil {
return false, err
}
return location.IsPostOffice(), nil
}
func branchToBool(data map[string]interface{}) string {
props, ok := data["props"]
if ok {
val, ok := (props.(map[string]interface{}))["value"]
if ok && val == "Yes" {
return "True"
}
}
return "False"
}
func countryComments(data map[string]interface{}) string {
props, ok := data["props"]
if ok {
comments, ok := (props.(map[string]interface{}))["comments"]
if ok {
return comments.(string)
}
}
return ""
}
// Put "complex" XML structures here where they output from another template
func (p applicationPackager) telephone(data map[string]interface{}) (template.HTML, error) {
return p.xmlTemplate("telephone.xml", data)
}
func (p applicationPackager) telephoneNoTimeOfDay(data map[string]interface{}) (template.HTML, error) {
return p.xmlTemplate("telephone-no-time-of-day.xml", data)
}
func (p applicationPackager) name(data map[string]interface{}) (template.HTML, error) {
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("name.xml", data, fmap)
}
func (p applicationPackager) nameLastFirst(data map[string]interface{}) (template.HTML, error) {
return p.xmlTemplate("name-last-first.xml", data)
}
func (p applicationPackager) daterange(data map[string]interface{}) (template.HTML, error) {
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("date-range.xml", data, fmap)
}
func (p applicationPackager) monthYearDaterange(data map[string]interface{}) (template.HTML, error) {
fmap := p.templateFMap()
fmap["date"] = p.monthYear // This is the only place where we swap out what the default list of functions uses.
return p.xmlTemplateWithFuncs("date-range.xml", data, fmap)
}
func (p applicationPackager) dateOptional(d, dnk map[string]interface{}) (template.HTML, error) {
view := make(map[string]interface{})
view["Date"] = d
view["DoNotKnow"] = dnk
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("date-month-day-year-optional.xml", view, fmap)
}
func (p applicationPackager) date(data map[string]interface{}) (template.HTML, error) {
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("date-month-day-year.xml", data, fmap)
}
func (p applicationPackager) monthYearOptional(d, dnk map[string]interface{}) (template.HTML, error) {
view := make(map[string]interface{})
view["Date"] = d
view["DoNotKnow"] = dnk
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("date-month-year-optional.xml", view, fmap)
}
func (p applicationPackager) monthYear(data map[string]interface{}) (template.HTML, error) {
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("date-month-year.xml", data, fmap)
}
func (p applicationPackager) location(data map[string]interface{}) (template.HTML, error) {
return p.locationOverrideLayout(data, "")
}
func street(data map[string]interface{}) string {
s1, _ := data["street"].(string)
s2, _ := data["street2"].(string)
if s2 != "" {
return s1 + " " + s2
}
return s1
}
func (p applicationPackager) militaryAddress(data map[string]interface{}) (template.HTML, error) {
return p.locationOverrideLayout(data, api.LayoutMilitaryAddress)
}
// location assumes the data comes in as the props
func (p applicationPackager) locationOverrideLayout(data map[string]interface{}, override string) (template.HTML, error) {
// Deserialize the initial payload from a JSON structure
payload := &api.Payload{}
// entity, err := payload.UnmarshalEntity(getInterfaceAsBytes(data))
entity, err := payload.UnmarshalEntity(getInterfaceAsBytes(data))
if err != nil {
return template.HTML(""), err
}
location := entity.(*api.Location)
domestic := location.IsDomestic()
postoffice := location.IsPostOffice()
// XXX
// Work-around issue in UI where it does not
// normalize case of state abbrevations. See:
// https://github.com/18F/e-QIP-prototype/issues/716
fmap := p.templateFMap()
// XXX
// Work-around issue in UI where it does not
// collect the address in the correct layout. See:
// https://github.com/18F/e-QIP-prototype/issues/755
layout := location.Layout
if override != "" {
layout = override
}
switch layout {
case api.LayoutOffense:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state-zipcode-county.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutBirthPlace:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state-county.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutBirthPlaceNoUS:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state-county-no-country.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutBirthPlaceWithoutCounty:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutBirthPlaceWithoutCountyNoUS:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state-no-country.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutCountry:
return p.xmlTemplateWithFuncs("location-country.xml", data, fmap)
case api.LayoutUSCityStateInternationalCity:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutUSCityStateInternationalCityCountry:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutState:
return p.xmlTemplateWithFuncs("location-state.xml", data, fmap)
case api.LayoutCityState:
return p.xmlTemplateWithFuncs("location-city-state.xml", data, fmap)
case api.LayoutStreetCityCountry:
return p.xmlTemplateWithFuncs("location-street-city-country.xml", data, fmap)
case api.LayoutCityCountry:
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutUSCityStateZipcodeInternationalCity:
if domestic {
return p.xmlTemplateWithFuncs("location-city-state-zipcode.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-city-country.xml", data, fmap)
case api.LayoutCityStateCountry:
return p.xmlTemplateWithFuncs("location-city-state-country.xml", data, fmap)
case api.LayoutUSAddress:
return p.xmlTemplateWithFuncs("location-street-city-state-zipcode.xml", data, fmap)
case api.LayoutStreetCity:
return p.xmlTemplateWithFuncs("location-street-city.xml", data, fmap)
case api.LayoutMilitaryAddress:
return p.xmlTemplateWithFuncs("location-address-apofpo-state-zipcode.xml", data, fmap)
case api.LayoutPhysicalDomestic:
return p.xmlTemplateWithFuncs("location-physical-address-domestic.xml", data, fmap)
case api.LayoutPhysicalInternational:
return p.xmlTemplateWithFuncs("location-physical-address-international.xml", data, fmap)
default:
if domestic || postoffice {
return p.xmlTemplateWithFuncs("location-street-city-state-zipcode.xml", data, fmap)
}
return p.xmlTemplateWithFuncs("location-street-city-country.xml", data, fmap)
}
}
func (p applicationPackager) countryValue(data map[string]interface{}) (template.HTML, error) {
return p.xmlTemplate("country.xml", data)
}
func (p applicationPackager) treatment(data map[string]interface{}) (template.HTML, error) {
fmap := p.templateFMap()
return p.xmlTemplateWithFuncs("treatment.xml", data, fmap)
}
func padDigits(digits string) string {
if digits == "" {
return ""
}
return fmt.Sprintf("%02s", digits)
}
func toUpper(state string) string {
return strings.ToUpper(state)
}
func derivedBasis(v string) string {
basis := map[string]string{
"Individual": "ByOperationofLaw",
"Other": "Other",
}
return basis[v]
}
func naturalizedBasis(v string) string {
basis := map[string]string{
"Individual": "BasedOnMyOwnIndividualNaturalizationApplication",
"Other": "Other",
}
return basis[v]
}
func dischargeType(v string) string {
basis := map[string]string{
"Honorable": "Honorable",
"Dishonorable": "Dishonorable",
"LessThan": "OtherThanHonorable",
"General": "General",
"BadConduct": "BadConduct",
"Other": "Other",
}
return basis[v]
}
func frequencyType(v string) string {
basis := map[string]string{
"OneTime": "Onetime",
"Future": "Future",
"Continuing": "Continuing",
"Other": "Other",
}
return basis[v]
}
func severanceType(v string) string {
basis := map[string]string{
"Fired": "Fired",
"Quit": "QuitKnowingWouldBeFired",
"Charges": "AllegedMisconduct",
"Performance": "UnsatisfactoryPerformance",
}
return basis[v]
}
func agencyType(v string) string {
basis := map[string]string{
"U.S. Department of Defense": "Defense",
"U.S. Department of State": "State",
"U.S. Office of Personnel Management": "OPM",
"Federal Bureau of Investigation": "FBI",
"U.S. Department of Treasury": "Treasury",
"U.S. Department of Homeland Security": "HomelandSecurity",
"Foreign government": "ForeignGovernment",
"Other": "Other",
}
return basis[v]
}
func clearanceType(v string) string {
basis := map[string]string{
"None": "None",
"Confidential": "Confidential",
"Secret": "Secret",
"Top Secret": "TopSecret",
"Sensitive Compartmented Information": "SCI",
"Q": "Q",
"L": "L",
"Issued by foreign country": "Foreign",
"Other": "Other",
}
return basis[v]
}
func hairType(v string) string {
basis := map[string]string{
"Bald": "Bald",
"Black": "Black",
"Blonde": "Blonde or Strawberry",
"Blue": "Blue",
"Brown": "Brown",
"Gray": "Gray or Partially Gray",
"Green": "Green",
"Orange": "Orange",
"Pink": "Pink",
"Purple": "Purple",
"Red": "Red or Auburn",
"Sandy": "Sandy",
"Unknown": "Unspecified or unknown",
"White": "White",
}
return basis[v]
}
func suffixType(s string) string {
if s == "Other" {
return "__Other__"
}
return s
}
func treatmentAnswerType(s string) string {
if s == "Decline" {
return "IDeclineToAnswer"
}
return s
}
// XXX
// Work-around for https://github.com/18F/e-QIP-prototype/issues/858
func drugType(d string) string {
switch d {
case "Cocaine", "THC", "Ketamine", "Narcotics", "Stimulants", "Depressants", "Hallucinogenic", "Steroids", "Inhalants":
return d
default:
return "Other"
}
}
// inc adds 1 to a
func inc(a interface{}) (interface{}, error) {
return add(a, 1)
}
// add returns the sum of a and b.
// Snagged from https://github.com/hashicorp/consul-template/blob/de2ebf4/template_functions.go
func add(b, a interface{}) (interface{}, error) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return av.Int() + bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return av.Int() + int64(bv.Uint()), nil
case reflect.Float32, reflect.Float64:
return float64(av.Int()) + bv.Float(), nil
default:
return nil, fmt.Errorf("add: unknown type for %q (%T)", bv, b)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return int64(av.Uint()) + bv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return av.Uint() + bv.Uint(), nil
case reflect.Float32, reflect.Float64:
return float64(av.Uint()) + bv.Float(), nil
default:
return nil, fmt.Errorf("add: unknown type for %q (%T)", bv, b)
}
case reflect.Float32, reflect.Float64:
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return av.Float() + float64(bv.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return av.Float() + float64(bv.Uint()), nil
case reflect.Float32, reflect.Float64:
return av.Float() + bv.Float(), nil
default:
return nil, fmt.Errorf("add: unknown type for %q (%T)", bv, b)
}
default:
return nil, fmt.Errorf("add: unknown type for %q (%T)", av, a)
}
}