<resource schema="theossa">
	<meta name="creationDate">2010-11-11T15:00:00</meta>
	<meta name="schema-rank">20</meta>
	<meta name="description">TheoSSA provides spectral energy distributions
		based on model atmosphere calculations.  Currently, we serve
		results obtained using the Tübingen NLTE Model Atmosphere Package
		(TMAP) for hot compact stars.
	</meta>
	<meta name="creator">Rauch, T.</meta>
	<meta name="title">TheoSSA - Theoretical Stellar Spectra Access</meta>

	<meta name="subject">spectroscopy</meta>
	<meta name="subject">astronomical-simulations</meta>
	<meta name="subject">post-asymptotic-giant-branch-stars</meta>
	<meta name="subject">stellar-atmospheres</meta>

	<meta name="source">2018MNRAS.475.3896R</meta>
	<meta name="type">Archive</meta>
	<meta name="type">Simulation</meta>
	<meta name="contact">
		<meta name="name">Thomas Rauch</meta>
		<meta name="email">gavo@listserv.uni-tuebingen.de</meta>
		<meta name="telephone">++49 7071 2978614</meta>
	</meta>

	<meta name="coverage.waveband">Optical</meta>
	<meta name="coverage.waveband">UV</meta>

	<meta name="dataSource">theory</meta>
	<meta name="ssap.dataSource">theory</meta>
	<meta name="ssap.creationType">archival</meta>
	<meta name="ssap.testQuery">MAXREC=1</meta>

	<meta name="copyright">
When publishing research making use of this service, please acknowledge:
"The TheoSSA service (http://dc.g-vo.org/theossa) used to retrieve
theoretical spectra
for this paper was constructed as part of the activities of
the German Astrophysical Virtual Observatory."
	</meta>

	<meta name="_bottominfo" format="raw"><![CDATA[
		<script type="text/javascript">
			function addLegend() {
				var nCols = $("table[class='results']>thead>tr>th").length;
				var content = "<tr class='mflegend'><td align='center' colspan='"
					+nCols
					+"'><em>All abundances are given as mass fractions</em></td></tr>";
				$("tr:has('th')").after($(content));
			}
			addLegend();
		</script>
	]]></meta>

	<meta name="_related" title="Compute custom SEDs"
		>http://astro.uni-tuebingen.de/~TMAW/TMAW.shtml</meta>

	<macDef name="elements">
		item, inLabel
		H,    H
		He,   HE
		Li,   LI
		Be,   BE
		B,    B
		C,    C
		N,    N
		O,    O
		F,    F
		Ne,   NE
		Na,   NA
		Mg,   MG
		Al,   AL
		Si,   SI
		P,    P
		S,    S
		Cl,   CL
		Ar,   AR
		K,    K
		Ca,   CA
		Sc,   SC
		Ti,   TI
		V,    V
		Cr,   CR
		Mn,   MN
		Fe,   FE
		Co,   CO
		Ni,   NI
		Zn,   ZN
		Ga,   GA
		Ge,   GE
		Kr,   KR
		Mo,   MO
		Tc,   TC
		Sn,   SN
		Xe,   XE
		Ba,   BA
	</macDef>


	<table id="raw_data" onDisk="True" primary="accref"
			dupePolicy="overwrite" adql="hidden" namePath="//ssap#instance">
		<mixin>//products#table</mixin>
		<mixin>//ssap#plainlocation</mixin>
		<index columns="ssa_targname" name="local_ssa_targname"/>
		<index columns="ssa_creatordid"/>
		<index columns="ssa_specstart"/>
		<index columns="ssa_specend"/>

		<LOOP listItems="ssa_dstitle ssa_creatorDID ssa_cdate ssa_pdate
				ssa_cversion ssa_targname ssa_redshift
				ssa_aperture ssa_specstart ssa_specend ssa_length ssa_creator
				ssa_collection ssa_instrument ssa_reference
				ssa_binSize ssa_specres">
			<events>
				<column original="\item"/>
			</events>
		</LOOP>

		<column name="t_eff"
			description="Effective temperature assumed"
			ucd="phys.temperature.effective" unit="K"
			tablehead="Eff. Temp." verbLevel="9">
			<values nullLiteral="-1"/>
		</column>
		<column name="log_g"
			description="Log of surface gravity assumed"
			ucd="phys.gravity" unit="cm/s**2" tablehead="Log Grav."
			verbLevel="9"/>
		<column name="mdot" type="real"
			description="Mass loss rate"
			ucd="phys.mass.loss" unit="solMass/yr" tablehead="Mass loss rate"
			verbLevel="9"/>

		<LOOP csvItems="\elements">
			<events>
				<column name="w_\item" ucd="phys.abund"
					tablehead="\item"
					description="Mass fraction of \item in the model computed."
					verbLevel="19"/>
			</events>
		</LOOP>

	</table>

	<data id="import" updating="True">
		<recreateAfter>make_view</recreateAfter>
		<property key="previewDir">previews</property>
		<sources pattern="spec/*.meta" recurse="True"/>
		<property name="stagingDir">spec/upload</property>

		<keyValueGrammar enc="utf-8">
			<mapKeys>accsize:Access.Size,
				ssa_length:Dataset.Length,
				ssa_cdate:DataID.Date,
				ssa_creatorDID: DataID.CreatorDID,
				ssa_dstitle: DataID.Title, ssa_creator:DataID.Creator,
				ssa_collection: DataID.Collection, ssa_cversion:DataID.Version,
				specstart:Char.SpectralAxis.Coverage.Bounds.Start,
				specend:Char.SpectralAxis.Coverage.Bounds.Stop,
				t_eff:Teff, log_g:logg,
				ssa_targname:Target.Name,
				alpha: Char.SpatialAxis.Coverage.Location.Value.C1,
				delta: Char.SpatialAxis.Coverage.Location.Value.C2,
				ssa_aperture: Char.SpatialAxis.Coverage.Bounds.Extent
				<LOOP csvItems="\elements">
					<events>
						<map dest="w_\item">Mass.Fraction.\inLabel</map>
					</events>
				</LOOP>
			</mapKeys>

			<rowfilter>
				<doc>For reasons of project sillyness, Tübingen would only upload
				metadata, and we would pull the associate data as we needed it
				(i.e., when making previews).  We used .txt and .vot extensions
				as actual accrefs; now, we only have .vot, and we artificially
				put that on the accref.</doc>
				<setup>
					<code>
						dlAccessURL = rd.getById("sdl").getURL("dlget", absolute=True)
					</code>
				</setup>
				<code>
					@baseAccref = os.path.splitext(\inputRelativePath{True})[0]+".vot"
					@path = "%s?ID=%s"%(
						dlAccessURL,
						getStandardPubDID(@baseAccref))
					yield row
				</code>
			</rowfilter>

			<rowfilter procDef="//products#define">
				<bind key="accref">@baseAccref</bind>
				<bind key="path">@path</bind>
				<bind key="mime">"application/x-votable+xml"</bind>
				<bind key="table">"\schema.data"</bind>
				<bind key="fsize">@accsize</bind>
				<bind key="preview_mime">"image/png"</bind>
				<bind key="preview">"theossa/previews/"+getFlatName(
					"theossa/"+os.path.splitext(
						os.path.basename(rowIter.sourceToken))[0]+".txt")</bind>
			</rowfilter>

			<rowfilter name="fillMissing">
				<!-- set some columns missing in old data with NULLs -->
				<code>
					for key in ["ssa_targname", "alpha", "delta", "ssa_aperture"]:
						row[key] = row.get(key)
					yield row
				</code>
			</rowfilter>
		</keyValueGrammar>

		<make table="raw_data">
			<rowmaker idmaps="*" id="make_raw_data">
				<var name="spectrumid">\srcstem</var>
				
				<apply name="fixupMeta">
					<code>
						if vars["ssa_collection"]=="TMAP":
							vars["ssa_instrument"] = "TMAP NLTE model-atmosphere spectra"
							vars["ssa_reference"] = "2003ASPC..288..103R"
					</code>
				</apply>

				<apply name="infer_bin_size">
					<code>
						@ssa_binSize = (float(@specend)-float(@specstart)
							)/float(@ssa_length)/1e10
					</code>
				</apply>

				<!-- some items have been added later; for those, old files
				give no values; we want the 0 anyway so as to avoid confusing
				users (NULL>0 is False). -->
				<LOOP listItems="mdot w_Ge w_Kr w_Xe w_Sn w_Al w_Zn w_Ba">
					<events>
						<map dest="\item">@\item or 0</map>
					</events>
				</LOOP>

				<map key="ssa_targname">@ssa_targname or "model star"</map>
				<map key="ssa_location" nullExcs="TypeError">
					pgsphere.SPoint.fromDegrees(float(@alpha), float(@delta))</map>
				<map key="ssa_pdate">datetime.datetime.utcnow()</map>
				<map key="ssa_specstart">float(@specstart)/1e10</map>
				<map key="ssa_specend">float(@specend)/1e10</map>

				<!-- Spectral resolution is wrong in upstream metadata -->
				<map key="ssa_specres">@ssa_binSize</map>
			</rowmaker>
		</make>
	</data>

	<table id="data" onDisk="True">
		<meta name="description">TheoSSA metadata in the SSAP schema.</meta>
		<meta>
			_associatedDatalinkService.serviceId:sdl
			_associatedDatalinkService.idColumn:ssa_pubDID
		</meta>
		<mixin
			sourcetable="raw_data"
			copiedcolumns="*"
			ssa_creationtype="'archival'"
			ssa_datasource="'theory'"
			ssa_fluxcalib="'ABSOLUTE'"
			ssa_speccalib="'ABSOLUTE'"
			ssa_redshift="0"
			ssa_spectralunit="'Angstrom'"
			ssa_fluxunit="'0.1J/(m**2.s.m)'"
			ssa_spectralucd="'em.wl'"
			ssa_fluxucd="'phot.flux.density'"
			ssa_targclass="'star'"
		>//ssap#view</mixin>
		<mixin
			calibLevel="2"
			facility_name="'IAAT'"
			instrument_name="'TMAW'"
			createDIDIndex="True">//obscore#publishSSAPMIXC</mixin>
	</table>

	<data id="make_view" auto="False">
		<make table="data"/>
	</data>

	<table id="spectrum">
		<mixin ssaTable="data"
			spectralDescription="Wavelength"
			fluxDescription="Stellar surface flux density"
		>//ssap#sdm-instance</mixin>
		<column name="flux_norm" type="double precision"
			ucd="phot.flux.density;arith.ratio"
			tablehead="Cont. Norm."
			description="Continuum normalised flux (as flux/continuum)"/>
		<column name="flux_cont" type="double precision"
			ucd="phot.flux.density;spect.continuum" unit="0.1J/(m**2.s.m)"
			tablehead="Continuum"
			description="Continuum flux"/>
	</table>

	<!-- first, a registration record for the underlying service
	(the one that actually computes the spectra) -->
	<service id="tmaw" allowed="external">
		<nullCore/>
		<meta>
			title: TMAW -- Tübingen Model Atmosphere Package WWW Interface
			referenceURL: http://astro.uni-tuebingen.de/~TMAW/
			creationDate: 2011-11-02T14:00:00
			shortName: TMAW
		</meta>
		<meta name="relatedTo" ivoId="ivo://org.gavo.dc/theossa/q/ssa"
			>TheoSSA</meta>
		<meta name="description" format="rst"><![CDATA[
		The Tübingen NLTE Model-Atmosphere Package (`TMAP
		<http://astro.uni-tuebingen.de/%7ETMAP/TMAP.html>`_) is a tool to calculate
		stellar atmospheres in spherical or plane-parallel geometry in hydrostatic
		and radiative equilibrium allowing departures from local thermodynamic
		equilibrium (LTE) for the population of atomic levels./TMAP/ is based on
		the so-called Accelerated Lambda Iteration (ALI) method and is able to
		account for line blanketing by metals. All elements from hydrogen to nickel
		may be included in the calculation with model atoms which are tailored for
		the aims of the user.
		]]></meta>
		<publish render="external" sets="ivo_managed">
			<meta name="accessURL">http://astro.uni-tuebingen.de/~TMAW</meta>
		</publish>
		<outputTable original="spectrum"/>
	</service>


	<service id="gettxt" allowed="qp">
		<property name="queryField">accref</property>
		<meta name="title">Theossa text format spectrum deliverer</meta>
		<!-- Sadly, the files from Tübingen come without headers.  This
		core adds headers to them.  We've asked them to change that. -->
		<customCore id="mktxt" module="res/textcore">
		</customCore>
	</service>

	<!-- the data that makes the VOTables for datalink and friends;
	there is some overlap with textcore; it would be worthwhile to
	join the two when next porting things.-->
	<data id="getdata" auto="false">
		<embeddedGrammar>
			<iterator>
				<setup>
					<code>
					import gzip
					import io
					import re
					from gavo import rsc
					from gavo import svcs

					_ACCPATH_RE = re.compile("Access.Reference=([^\n]*)")

					def getSrcForAccref(accref):
						# Figure out where the text data is; we pull it
						# from the URL specified in the meta file, which again
						# we find by replacing whatever extension we have with
						# "meta"
						metaPath = os.path.join(base.getConfig("inputsDir"),
							os.path.splitext(accref)[0]+".meta")
						with open(metaPath, "r", encoding="utf-8") as f:
							return _ACCPATH_RE.search(f.read()).group(1)

					def parseTMAPText(inBytes):
						if inBytes.startswith(b"\\x1f\\x8b"):
							inBytes = gzip.GzipFile(fileobj=io.BytesIO(inBytes)).read()
						dataPairs, cDID = [], None
						inStr = inBytes.decode("utf-8")

						for line in inStr.split("\n"):
							if not line.strip():
								# ignore empty lines
								pass	
							elif line.startswith("*"):
								# Metadata, ignore
								pass
							else:
								# all other lines must be n cols of data
								parts = line.split()
								lam, flux = parts[:2]
								normalized = continuum = None
								if len(parts)==3:
									normalized = float(parts[2])
								elif len(parts)==4:
									normalized = float(parts[2])
									continuum = float(parts[3])

								yield {"spectral": float(lam),
									"flux": float(flux),
									"flux_norm": normalized,
									"flux_cont": continuum}

					cacheDir = os.path.join(parent.rd.resdir, "txcache")
					</code>
				</setup>
				<code>
				accref = self.sourceToken["accref"]
				try:
					srcURL = getSrcForAccref(accref)
					src = utils.getWithCache(srcURL, cacheDir)
				except IOError as msg:
					raise svcs.UnknownURI("The upstream data at %s is not"
						"available or does not exist (%s)"%(srcURL, str(msg)))
				return parseTMAPText(src)
				</code>
			</iterator>
		</embeddedGrammar>
		<make table="spectrum">
			<parmaker>
				<apply procDef="//ssap#feedSSAToSDM"/>
				<!-- fix the accref; as it's coming in, it's the one of the
				text record, but we want that of the VOTable. -->
				<map dest="accref">vars["parser_"].sourceToken["accref"
					][:-3]+"vot"</map>
			</parmaker>
		</make>
	</data>

	<service id="sdl" allowed="dlget,dlmeta">
		<meta name="title">TheoSSA Datalink Service</meta>
		<datalinkCore>
			<descriptorGenerator procDef="//soda#sdm_genDesc">
				<bind name="ssaTD">"\rdId#data"</bind>
			</descriptorGenerator>
			<dataFunction procDef="//soda#sdm_genData">
				<bind name="builder">"\rdId#getdata"</bind>
			</dataFunction>
			<FEED source="//soda#sdm_plainfluxcalib"/>
			<FEED source="//soda#sdm_cutout"/>
			<FEED source="//soda#sdm_format"/>
		</datalinkCore>
	</service>

	<service id="ssa" allowed="form,ssap.xml">
		<meta name="shortName">theossa</meta>
		<meta name="derivedFrom" ivoId="ivo://org.gavo.dc/theossa/q/tmaw"
			>TMAW</meta>
		<meta name="_longdoc" format="rst">
SSA services are particularly well suited for programmatic access.  Here
is a sample program using pyVO_ and astropy to plot a T_eff grid::

  from astropy.table import Table
  import pyvo
  import numpy as np

  TEFF_SCALER = 200.


  def _wiggle(val):
    """returns a +/- 2% range for the float val in SSAP syntax.
    """
    if val is not None:
      return "%s/%s"%(val*0.98, val*1.02)


  def discover_spectra(teff=None, logg=None, **elements):
    """returns a pyvo result for spectra with teff, logg, and given abundances.

    Each argument, when given, must be a float.  The function "wiggles"
    them by +/- 10%.
    """
    pars = {"format": "votable"}
    pars["t_eff"] = _wiggle(teff)
    pars["log_g"] = _wiggle(logg)
    for el, abundance in elements.items():
      pars["w_%s"%el] = _wiggle(abundance)

    svc = pyvo.dal.SSAService("http://dc.g-vo.org/theossa/q/ssa/ssap.xml")
    return svc.create_query(**pars).execute()


  def plot_spectrum(ax, spectrum):
    """plots spectrum (an SSA result from theossa) into fig, offset by teff,
    into the matplotlib axes ax.
    """
    teff = spectrum.get("t_eff")
    t = Table.read(spectrum.acref)
    ax.plot(t["spectral"], np.log(t["flux"])+teff/TEFF_SCALER)
    ax.text(max(t["spectral"]), teff/TEFF_SCALER, str(teff),
      horizontalalignment="right")


  def make_plot(spectra):
    """arranges spectra in a plot, showing evolution by teff.
    """
    from matplotlib import pyplot
    fig = pyplot.figure(figsize=(4,6))
    ax = fig.add_axes([0,0,1,1])

  	for sp in spectra:
    	plot_spectrum(ax, sp)

  	with open("combined.pdf", "wb") as f:
    	pyplot.savefig(f, format="pdf")


  if __name__=="__main__":
    specs = discover_spectra(logg=6, C=0.0034, He=0.284, H=0.715)
    make_plot(specs)

.. _pyVO: https://github.com/astropy/pyvo

		</meta>

		<publish render="ssap.xml" sets="ivo_managed"/>

		<ssapCore queriedTable="data">
			<property name="previews">auto</property>
			<FEED source="//ssap#hcd_condDescs"/>
			<condDesc buildFrom="t_eff"/>
			<condDesc buildFrom="log_g"/>
			<condDesc buildFrom="mdot"/>
			<LOOP csvItems="\elements">
				<events>
					<condDesc buildFrom="w_\item"/>
				</events>
			</LOOP>
		</ssapCore>
	</service>

	<condDesc id="massfrac_cond" combining="True">
		<!-- base code for mass fraction conditions.  This assumes the
		keys as given in massfrac_template -->
		<phraseMaker>
			<code><![CDATA[
				elKey, minKey, maxKey = inputKeys
				elName, minVal, maxVal = (inPars.get(elKey.name),
					inPars.get(minKey.name), inPars.get(maxKey.name))

				if ((minVal is not None or maxVal is not None)
						and elName is None):
					raise base.ValidationError("Sorry, you must choose an"
						" element.  Yes, we should not offer ANY in the first place."
						"  We'll stop at some point.", elKey.name)
				if (not elKey.values._validateOptions(elName) or
						(minVal is None and maxVal is None)):
					return
				colName = "w_"+elName

				if minVal is None:
					yield "%s<=%%(%s)s"%(colName,
						base.getSQLKey(maxKey.name, maxVal, outPars))
				elif maxVal is None:
					yield "%s>=%%(%s)s"%(colName,
						base.getSQLKey(minKey.name, minVal, outPars))
				else:
					yield "%s BETWEEN %%(%s)s AND %%(%s)s"%(colName,
						base.getSQLKey(minKey.name, minVal, outPars),
						base.getSQLKey(maxKey.name, maxVal, outPars))
			]]></code>
		</phraseMaker>
	</condDesc>

	<STREAM id="massfrac_template">
		<doc>A condDesc for a mass fraction.  These consist of an element label,
		a center value and a weird way of specifying the error.
		
		There can be a few of them for a given service, and thus you need to
		define the macro item. It is used to disambiguate the names.
		</doc>
		<condDesc original="massfrac_cond">
			<inputKey name="el\item" type="text" tablehead="Element \item"
					description="Element for mass fraction \item"
					multiplicity="single">
				<values>
					<LOOP csvItems="\elements">
						<events>
							<option>\item</option>
						</events>
					</LOOP>
				</values>
			</inputKey>

			<inputKey name="mfmin\item" tablehead="Min. Mass Fraction \item"
					description="Mass fraction \item, lower bound of interval"
					multiplicity="single">
				<property name="cssClass">a_min</property>
			</inputKey>

			<inputKey name="mfmax\item" tablehead="Max. Mass Fraction \item"
					description="Mass fraction \item, upper bound of interval"
					multiplicity="single">
				<property name="cssClass">a_max</property>
			</inputKey>

			<group name="mf\item">
				<description>Mass fraction of an element. You may leave out
					either upper or lower bound.</description>
				<property name="label">Mass Fraction \item</property>
				<property name="style">compact</property>
			</group>
		</condDesc>
	</STREAM>

	<service id="reload" allowed="form" limitTo="rauch"
			defaultRenderer="form">
		<meta name="title">TheoSSA Manhole</meta>
		<customCore module="res/reloadcore"/>
	</service>

	<service id="upload" allowed="upload,mupload" limitTo="rauch">
		<meta name="shortName">theossa_update</meta>
		<meta name="title">Theoretical Stellar Spectra Upload</meta>
		<meta name="creationDate">2007-11-20T12:00:00Z</meta>
		<meta name="description">Upload of computed spectra in files
			containing key-value pairs.</meta>
		<meta name="rights">proprietary</meta>

		<uploadCore destDD="import"/>
	</service>


	<service id="delete" limitTo="rauch">
		<meta name="title">Internal Maintenance</meta>
		<customCore id="delete_core" module="res/deletecore">
			<property name="targetTable">raw_data</property>
			<property name="targetDD">import</property>
		</customCore>
	</service>


	<service id="web">
		<meta name="shortName">tmap_web</meta>
		<meta name="title">TheoSSA TMAP Web Interface</meta>
		<meta name="derivedFrom" ivoId="ivo://org.gavo.dc/theossa/q/tmaw"
			>TMAW</meta>
		<publish render="form" sets="ivo_managed,local"/>
		
		<dbCore queriedTable="data">
			<FEED source="//procs#rangeCond"
				name="t_eff"
				groupdesc="the atmosphere's effective temperatures to include"
				grouplabel="Effective Temperature [K]"/>

			<FEED source="//procs#rangeCond"
				name="log_g"
				groupdesc="surface gravities to include"
				grouplabel="Log. Surface gravity [cm/s2]"/>

			<FEED source="//procs#rangeCond"
				name="mdot"
				groupdesc="mass loss rates to include"
				grouplabel="Mass loss rate [solMass/yr]"/>

			<LOOP listItems="1 2 3" source="massfrac_template"/>

			<condDesc>
				<inputKey original="data.ssa_targname" tablehead="Standard Stars">
					<values fromdb="ssa_targname from theossa.data
						where ssa_targname!='model star' order by ssa_targname"/>
				</inputKey>
			</condDesc>

			<condDesc>
				<phraseMaker>
					<code>
						yield "mime=%%(%s)s"%(
							base.getSQLKey("format", "application/x-votable+xml",
								outPars))
					</code>
				</phraseMaker>
			</condDesc>


		</dbCore>

		<outputTable>
			<outputField original="ssa_collection">
				<formatter><![CDATA[
					return T.a(href="http://astro.uni-tuebingen.de/~TMAW/")[data]
				]]></formatter>
			</outputField>
			<outputField original="accref" wantsRow="True">
				<formatter><![CDATA[
					baseAccref, ext = os.path.splitext(data["accref"])
					baseLink = makeProductLink(baseAccref)
					res = T.div()
					res[T.a(href="http://astro.uni-tuebingen.de/~TVIS/oplot/"
						"theossa/index.html?url="+urllib.parse.quote(baseLink+".txt"))[
						"[View in Browser]"], T.br]
					res[T.a(href=baseLink+".vot")[
						"[...as VOTable]"]]
					
					params = ["%s=%g .. %g"%(key, value*0.999, value*1.001)
						for key, value in data.items()
						if key.startswith("w_") and value]

					for parName in ["log_g_min", "log_g_max", "t_eff_min",
							"t_eff_max", "mdot_min", "mdot_max"]:
						if queryMeta.ctxArgs.get(parName):
							params.append("%s=%s"%(parName, queryMeta.ctxArgs[parName]))

					params.extend([
						"REQUEST=queryData",
						"__nevow_form__=genForm",
						"submit=Go",
						"FORMAT=text/plain",
						"VERB=1",
						"RESPONSEFORMAT=tar",
						"MAXREC=30000"])
					res[T.br, T.a(
						href="/\rdId/ssa/form?"+"&amp;".join(params),
						style="white-space:nowrap")[
							"[...a Teff-log g-grid for this chemisty", T.br,
								"and wavelength range]"]]

					return res(onmouseover="this.onmouseover=null;"
						"$(this).append($('<img style=\"margin-top:4px\"/>')"
							".attr('src', '%s'));"%(
						"/getproduct/"+baseAccref+".vot?preview=True"))
				]]></formatter>
			</outputField>
			<autoCols>t_eff,log_g,mdot,w_H,w_He,w_C,w_N,w_O,w_F,w_Na,
				w_Mg,w_Al,w_Si,w_P,w_S,w_Cl,w_Ar,w_Ca,w_Fe,w_Co,w_Ni,
				w_Ge, w_Kr, w_Sn, w_Xe, w_Al, w_Zn, w_Ba</autoCols>
			<outputField original="ssa_specstart" displayHint="displayUnit=Angstrom"/>
			<outputField original="ssa_specend" displayHint="displayUnit=Angstrom"/>
		</outputTable>

		<property name="customCSS">
			input.a_min {width: 5em}
			input.a_max {width: 5em}
			input.formkey_min {width: 6em!important}
			input.formkey_max {width: 6em!important}
			span.a_min:before { content:" between "; }
			span.a_max:before { content:" and "; }
			tr.mflegend td {
				padding-top: 0.5ex;
				padding-bottom: 0.5ex;
				border-bottom: 1px solid black;
			}
		</property>
	</service>

	<regSuite title="TheoSSA SSAP">
		<regTest title="Rauchspectra SSAP properly rejects query without REQUEST">
			<url POS="0.0000,0.0000" SIZE="0.3">ssa/ssap.xml</url>
			<code><![CDATA[
				self.assertHasStrings(
					'value="ERROR">Field REQUEST: Missing or invalid value for REQUEST.')
			]]></code>
		</regTest>

		<regTest title="Rauchspectra SSAP queryData looks right">
			<url CREATORDID="ivo://tmap.iaat/0038000_5.70_H_9.968E-01_HE_3.167E-03_02000-03000A_2008-08-02_07_20_01"
				VERB="3"
				REQUEST="queryData" w_h="0.995/1" RESPONSEFORMAT="votabletd"
					>ssa/ssap.xml</url>
			<code>
				self.assertXpath(
					"v:RESOURCE[@type='results']/v:INFO[@name='QUERY_STATUS']", {
					"value": "OK"})
				self.assertXpath("v:RESOURCE[@type='results']/v:INFO[@name="
						"'SERVICE_PROTOCOL']", {None: "SSAP"})
				self.assertXpath('v:RESOURCE/v:TABLE/v:FIELD[@name="accref"]', {
					"utype": "ssa:Access.Reference"})
				self.assertXpath("//v:TABLEDATA/v:TR[1]/v:TD["
					"count(//v:FIELD[@name='ssa_creatorDID']/preceding::v:FIELD)+1]", {
					None: "ivo://tmap.iaat/0038000_5.70_H_9.968E-01_HE_3.167E"
						"-03_02000-03000A_2008-08-02_07_20_01"})
				self.assertXpath("//v:TABLEDATA/v:TR[1]/v:TD["
					"count(//v:FIELD[@name='ssa_pubDID']/preceding::v:FIELD)+1]", {
					None: "ivo://org.gavo.dc/~?theossa/spec/spec_HHe/0038000_5.70_H_9"
					".968E-01_HE_3.167E-03_02000-03000A_2008-08-02_07_20_01.txt"})
				self.assertXpath("//v:TABLEDATA/v:TR[1]/v:TD["
					"count(//v:FIELD[@name='t_eff']/preceding::v:FIELD)+1]", {
					None: "38000.0"})
				self.assertXpath('//v:FIELD[@utype="ssa:DataID.DataSource"]',
					{"name": "ssa_datasource"})

				# "direct SODA" service
				self.assertXpath(
					"v:RESOURCE[@utype='adhoc:service' and "
						"v:PARAM[@name='standardID']/@value='ivo://ivoa.net/std/soda#"
						"sync-1.0']/v:GROUP[@name='inputParams']/v:PARAM[@name='BAND']"
						"/v:VALUES/v:MIN",
						{"value": EqualingRE("2(.00000002)?e-07")})

				# datalink meta service
				self.assertXpath(
					"v:RESOURCE[@utype='adhoc:service' and "
						"v:PARAM[@name='standardID']/@value='ivo://ivoa.net/std/datalink"
						"#links-1.1']/v:GROUP[@name='inputParams']/v:PARAM[@name='ID']",
						{"ref": "ssa_pubDID"})
			</code>
		</regTest>

		<regTest title="SSAP unsupported operation rejected">
			<url REQUEST="depp">ssa/ssap.xml</url>
			<code>
				self.assertXpath("*[@type='results']/v:INFO[@name='QUERY_STATUS']", {
				"value": "ERROR",
				None: "Field REQUEST: Missing or invalid value for REQUEST."})
			</code>
		</regTest>

		<regTest title="SSAP Metadata have standard and custom fields on I/O.">
			<url Request="queryData" formaT="METADATA">/theossa/q/ssa/ssap.xml</url>
			<code>
				self.assertXpath("v:RESOURCE/v:INFO[@name='QUERY_STATUS']", {
					"value": "OK"})
				self.assertXpath("v:RESOURCE/v:PARAM[@name='INPUT:POS']", {
					"utype": "ssa:Char.SpatialAxis.Coverage.Location.Value",
					"datatype": "char",
					"unit": "deg"})
				self.assertXpath(
					"v:RESOURCE/v:PARAM[@name='INPUT:POS']/v:DESCRIPTION", {
					None: "ICRS position of target object",})
				self.assertXpath("v:RESOURCE/v:PARAM[@name='INPUT:log_g']", {
					"ucd": "phys.gravity",
					"unit": "cm/s**2"})
				self.assertXpath(
					"v:RESOURCE/v:TABLE/v:FIELD[@utype='ssa:Access.Format']", {
					"name": "mime",
					"datatype": "char"})
			</code>
		</regTest>

		<regTest title="SSAP overflows are properly marked">
			<url REQUEST="queryData" MAXREC="1" RESPONSEFORMAT="votabletd"
				>ssa/ssap.xml</url>
			<code>
				self.assertXpath("v:RESOURCE/v:INFO[@name='QUERY_STATUS']", {
				"value": "OVERFLOW"})
			</code>
		</regTest>

		<regTest title="Rauch spectrum VOTable looks all right">
			<url httpHonorRedirects="True"
				>/getproduct/theossa/spec/spec_HHe/0038000_5.70_H_9.968E-01_HE_3.167E-03_02000-03000A_2008-08-02_07_20_01.vot</url>
			<code>
				self.assertXpath("v:RESOURCE[@utype='spec:Spectrum']", {})
				self.assertXpath("v:RESOURCE/v:TABLE/v:GROUP["
					"@utype='spec:Spectrum.Target']", {})
				self.assertXpath("//v:FIELD[@name='flux']", {
						"utype": "spec:spectrum.data.fluxaxis.value",
						"unit": "0.1J/(m**2.s.m)"})
				self.assertXpath("//v:PARAM[@name='accref']", {
					"value": EqualingRE(r"http://[^/]*/getproduct/theossa/spec/"
						"spec_HHe/0038000_5.70_H_9.968E-01_HE_3.167E-03_02000-03000A_"
						"2008-08-02_07_20_01.vot")})
				self.assertXpath("//v:PARAM[@name='ssa_length']", {
					"value": "10787",})
				self.assertXpath("v:RESOURCE/v:TABLE/v:DATA/v:BINARY/v:STREAM", {
					"encoding": "base64"})
			</code>
		</regTest>

		<regTest title="Theossa four-column data has proper metadata">
			<url httpHonorRedirects="True"
				>/getproduct/theossa/spec/upload/0079500_4.53_7.375E-01_2.493E-01_2.365E-03_00910-01188_2019-07-08_08_52_16.vot</url>

			<code>
				self.assertXpath("//v:FIELD[2]", {"name": "flux",
					"unit": "0.1J/(m**2.s.m)"})
				self.assertXpath("//v:FIELD[3]", {"name": "flux_norm",
					"ucd": "phot.flux.density;arith.ratio"})
				self.assertXpath("//v:FIELD[4]", {"name": "flux_cont",
					"ucd": "phot.flux.density;spect.continuum"})
				row = self.getFirstVOTableRow(rejectExtras=False)
				self.assertAlmostEqual(row["spectral"], 910.1)
				self.assertAlmostEqual(row["flux_norm"], 1.0)
				self.assertAlmostEqual(row["flux_cont"], 1.67424e+19)
			</code>
		</regTest>

		<regTest title="Theossa text delivery looks all right">
			<url httpHonorRedirects="True">/getproduct/theossa/spec/spec_HHe/0038000_5.70_H_9.968E-01_HE_3.167E-03_02000-03000A_2008-08-02_07_20_01.txt</url>
			<code>
				self.assertHasStrings("* Dataset.Length=10787",
					"* Mass.Fraction.HE=3.167E-03",
					"2.0000000E+03  6.204675E+17")
			</code>
		</regTest>
	</regSuite>

	<regSuite title="DaCHS regression" id="dachs_regression">
		<regTest title="tar of datalink products works">
			<url parSet="form" MAXREC="2"
				mime="application/x-votable+xml" _FORMAT="tar"
				>web/form</url>
			<code>
				self.assertHasStrings("dc_data/", ".vot", "0000000", "&lt;VOTABLE ",
					"spec:Spectrum")
			</code>
		</regTest>

		<regTest title="Auth required for protected upload.">
			<url>upload/upload</url>
			<code>
				self.assertHTTPStatus(401)
			</code>
		</regTest>

		<regTest title="Auth required for protected machine upload.">
			<url>upload/mupload</url>
			<code>
				self.assertHTTPStatus(401)
			</code>
		</regTest>

		<regTest title="TheoSSA previews delivered">
			<url>/getproduct/theossa/spec/spec_HHe/0038000_5.70_H_9.968E-01_HE_3.167E-03_02000-03000A_2008-08-02_07_20_01.vot?preview=True</url>
			<code>
				self.assertHasStrings("PNG", "PLTE")
			</code>
		</regTest>

		<regTest title="SSA positional constraint doesn't return rows without
				positions.">
			<url REQUEST="queryData" POS="0.0000,0.0000" SIZE="0.3"
				>ssa/ssap.xml</url>
			<code>
				self.assertEqual(len(self.getVOTableRows()), 0)
			</code>
		</regTest>

		<regTest title="Machine upload error is some text">
			<url parSet="form" httpAuthKey="theossa" httpMethod="POST" Mode="u">
				<httpUpload name="File" fileName="broken.txt">---</httpUpload
					>upload/mupload</url>
			<code>
				self.assertEqual(self.data,
					b"Field File: Cannot enter broken.txt in database: At unspecified location: Not a key value pair: '---'")
			</code>
		</regTest>
	</regSuite>

	<regSuite title="theossa datalink">
		<regTest title="Theossa Datalink gives plausible data">
			<url
				ID="ivo://org.gavo.dc/~?theossa/spec/spec_HHe/0038000_5.70_H_9.968E-01_HE_3.167E-03_02000-03000A_2008-08-02_07_20_01.txt"
				FORMAT="application/x-votable+xml;serialization=tabledata"
				BAND="2.09e-7 2.1e-7"
				>sdl/dlget</url>
			<code>
				self.assertHasStrings("2097.3", "5.304633e+17",
					'utype="spec:Spectrum.Curation.Reference"')
			</code>
		</regTest>

	</regSuite>

	<regSuite title="Theossa upload regression" id="reg_upload" sequential="True">
		<regTest title="Deleting an upload seems to work (may fail on state).">
			<url parSet="form" httpAuthKey="theossa" httpMethod="POST"
				spectrumid="0020000_4.01_000001-400000_2016-04-06_10_25_33">
				delete/form</url>
			<code><![CDATA[
				self.assertHasStrings(
						"<li>Spectrum id: 0020000_4.01_000001-400000_2016-04-06_10_25_33"
						"</li>",
						# 1 in text line is # of records dropped.
						'<tr class="data"><td>1</td></tr>')
			]]>
			</code>
		</regTest>

		<regTest title="Updating a non-existing upload warns.">
			<url parSet="form" httpAuthKey="theossa" httpMethod="POST" Mode="u">
				<httpUpload name="File"
					source="res/0020000_4.01_000001-400000_2016-04-06_10_25_33.meta"
					fileName="0020000_4.01_000001-400000_2016-04-06_10_25_33.meta"
					/>upload/mupload</url>
			<code>
				self.assertEqual(self.data,
					b"Uploading 0020000_4.01_000001-400000_2016-04-06_10_25_33.meta"
					b" did not change data database.\\nThis"
					b" usually happens when the file already existed for an insert"
					b" or did not exist for an update.\n")
			</code>
		</regTest>

		<regTest title="Inserting an upload works.">
			<url parSet="form" httpAuthKey="theossa" httpMethod="POST" Mode="i">
				<httpUpload name="File"
					source="res/0020000_4.01_000001-400000_2016-04-06_10_25_33.meta"
					fileName="0020000_4.01_000001-400000_2016-04-06_10_25_33.meta"
					/>upload/mupload</url>
			<code>
				self.assertEqual(self.data,
					b"0020000_4.01_000001-400000_2016-04-06_10_25_33.meta"
					b" uploaded, 1 records modified\n")
			</code>
		</regTest>

	</regSuite>
</resource>
