How to insert Identity value to another table in SQLXML - sqlxml

I am using SQLXML 4.0 to Bulkload data from the XML to the SQL Database. I have 2 tables. "Country" and "Customer". Below is my XML file format ("Data.xml")
<Root>
<Countries>
<Country Name="USA"></Country>
<Country Name="Australia"></Country>
</Countries>
<Customers>
<Customer Name="John Smith" CountryName="Australia"></Customer>
</Customers>
</Root>
I have 2 tables
Country
Id Name
1 USA
2 Australia
Customer
Id CustomerName CountryId
1 John Smith 2
When i import my XML, "Id" column in the country is auto generated as it is identity(1,1). I want this "Id" value to sit in my Customer Table "CountryId" Column.
Note that I cannot nest the Customer Tag within the Country Tag. By nesting, i can easily define Parent and Child key in the XSD. But without nesting the elements i have not found a way to define these relationships.
Below is my XSD (Schema.xml)
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sql="urn:schemas-microsoft-com:mapping-schema">
<xsd:annotation>
<xsd:appinfo>
<sql:relationship name="Country_Customer"
parent="Country"
parent-key="Id"
child="Customer"
child-key="CountryId" />
</xsd:appinfo>
</xsd:annotation>
<xsd:element name="Root" sql:is-constant="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Countries" sql:is-constant="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="unbounded" name="Country" sql:relation="Country">
<xsd:complexType>
<xsd:attribute name="Name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="Customers" sql:is-constant="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Customer" sql:relation="Customer" sql:relationship="Country_Customer">
<xsd:complexType>
<xsd:attribute name="Name" type="xsd:string" use="required" sql:field="CustomerName" />
<xsd:attribute name="CountryName" type="xsd:string" use="required" sql:field="CountryId" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
I have defined an SQL relationship but i am specifying the Country Name in my XML and hence the relationship does not work.
Below are the table creation scripts
CREATE TABLE [dbo].[Country](
[Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] [nvarchar](50) NULL,
)
CREATE TABLE [dbo].[Customer](
[Id] [bigint] IDENTITY(1,1) NOT NULL Primary Key,
[CustomerName] [nvarchar](50) NULL,
[CountryId] [bigint] NULL,
)
I am using the below VB Script to bulk import
Dim FileValid
set objBL = CreateObject("SQLXMLBulkLoad.SQLXMLBulkload.4.0")
objBL.ConnectionString = "provider=SQLOLEDB;data source=ServerName;database=databasename;User Id=username;Password=password"
objBL.ErrorLogFile = "c:\error.log"
objBL.KeepIdentity = False
'Validate the data file prior to bulkload
Dim sOutput
sOutput = ValidateFile("Data.xml", "", "Schema.xml")
WScript.Echo sOutput
If FileValid Then
' Check constraints and initiate transaction (if needed)
' objBL.CheckConstraints = True
' objBL.Transaction=True
'Execute XML bulkload using file.
objBL.Execute "Schema.xml", "Data.xml"
set objBL=Nothing
End If
Function ValidateFile(strXmlFile,strUrn,strXsdFile)
' Create a schema cache and add SampleSchema.xml to it.
Dim xs, fso, sAppPath
Set fso = CreateObject("Scripting.FileSystemObject")
Set xs = CreateObject("MSXML2.XMLSchemaCache.6.0")
sAppPath = fso.GetFolder(".")
xs.Add strUrn, sAppPath & "\" & strXsdFile
' Create an XML DOMDocument object.
Dim xd
Set xd = CreateObject("MSXML2.DOMDocument.6.0")
' Assign the schema cache to the DOM document.
' schemas collection.
Set xd.schemas = xs
' Load XML document as DOM document.
xd.async = False
xd.Load sAppPath & "\" & strXmlFile
' Return validation results in message to the user.
If xd.parseError.errorCode <> 0 Then
ValidateFile = "Validation failed on " & _
strXmlFile & vbCrLf & _
"=====================" & vbCrLf & _
"Reason: " & xd.parseError.reason & _
vbCrLf & "Source: " & _
xd.parseError.srcText & _
vbCrLf & "Line: " & _
xd.parseError.Line & vbCrLf
FileValid = False
Else
ValidateFile = "Validation succeeded for " & _
strXmlFile & vbCrLf & _
"======================" & _
vbCrLf & "Contents to be bulkloaded" & vbCrLf
FileValid = True
End If
End Function

Hope this may help: SQL Server XML

Related

How to search for some XML data and repleace it with a new value using Nokogiri Ruby gem

Base on below XML exemple file employees.xml and using Ruby Nokogiri gem I wan to open this file, change the building number to 320 and the room number to 99 for Sandra Defoe and save the changes. What is the recommended way to do it.
<?xml version="1.0" encoding="utf-16"?>
<employees>
<employee id="be129">
<firstname>Jane</firstname>
<lastname>Doe</lastname>
<building>327</building>
<room>19</room>
</employee>
<employee id="be130">
<firstname>William</firstname>
<lastname>Defoe</lastname>
<building>326</building>
<room>14a</room>
</employee>
<employee id="be132">
<firstname>Sandra</firstname>
<lastname>Defoe</lastname>
<building>327</building>
<room>22</room>
</employee>
<employee id="be133">
<firstname>Steve</firstname>
<lastname>Casey</lastname>
<building>327</building>
<room>24</room>
</employee>
</employees>
I'd use this:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="utf-16"?>
<employees>
<employee id="be130">
<firstname>William</firstname>
<lastname>Defoe</lastname>
<building>326</building>
<room>14a</room>
</employee>
<employee id="be132">
<firstname>Sandra</firstname>
<lastname>Defoe</lastname>
<building>327</building>
<room>22</room>
</employee>
</employees>
EOT
first_name = 'Sandra'
last_name = 'Defoe'
node = doc.at("//employee[firstname/text()='%s' and lastname/text()='%s']" % [first_name, last_name])
node.at('building').content = '320'
node.at('room').content = '99'
Which results in:
doc.to_xml
# => "\uFEFF<?xml version=\"1.0\" encoding=\"utf-16\"?>\n" +
# "<employees>\n" +
# " <employee id=\"be130\">\n" +
# " <firstname>William</firstname>\n" +
# " <lastname>Defoe</lastname>\n" +
# " <building>326</building>\n" +
# " <room>14a</room>\n" +
# " </employee>\n" +
# " <employee id=\"be132\">\n" +
# " <firstname>Sandra</firstname>\n" +
# " <lastname>Defoe</lastname>\n" +
# " <building>320</building>\n" +
# " <room>99</room>\n" +
# " </employee>\n" +
# "</employees>\n"
Normally I recommend using CSS selectors because they tend to result in less visual noise, however CSS doesn't let us peek into the text of nodes, and working around that, while possible, results in even more noise. XPath, on the other hand, can be very noisy, but for this sort of task, it's more usable.
XPath is very well documented and figuring out what this is doing should be pretty easy.
The Ruby side of it is using a "format string":
"//employee[firstname/text()='%s' and lastname/text()='%s']" % [first_name, last_name])
similar to
"%s %s" % [first_name, last_name] # => "Sandra Defoe"
"//employee[firstname/text()='%s' and lastname/text()='%s']" % [first_name, last_name]
# => "//employee[firstname/text()='Sandra' and lastname/text()='Defoe']"
Just for thoroughness, here's what I'd do if I wanted to use CSS exclusively:
node = doc.search('employee').find { |node|
node.at('firstname').text == first_name && node.at('lastname').text == last_name
}
This gets ugly though, because search tells Nokogiri to retrieve all employee nodes from libXML, then Ruby has to walk through them all telling Nokogiri to tell libXML to look in the child firstname and lastname nodes and return their text. That's slow, especially if there are many employee nodes and the one you want is at the bottom of the file.
The XPath selector tells Nokogiri to pass the search to libXML which parses it, finds the employee node with the child nodes containing the first and last names and returns only that node. It's much faster.
Note that at('employee') is equivalent to search('employee').first.
# File 'lib/nokogiri/xml/searchable.rb', line 70
def at(*args)
search(*args).first
end
Finally, mediate on the difference between a NodeSet#text and Node#text as the first will lead to insanity.
Assume your content is a string:
xml=%q(
<?xml version="1.0" encoding="utf-16"?>
<employees>
<employee id="be129">
<firstname>Jane</firstname>
<lastname>Doe</lastname>
<building>327</building>
<room>19</room>
</employee>
<employee id="be130">
<firstname>William</firstname>
<lastname>Defoe</lastname>
<building>326</building>
<room>14a</room>
</employee>
<employee id="be132">
<firstname>Sandra</firstname>
<lastname>Defoe</lastname>
<building>327</building>
<room>22</room>
</employee>
<employee id="be133">
<firstname>Steve</firstname>
<lastname>Casey</lastname>
<building>327</building>
<room>24</room>
</employee>
</employees>)
doc = Nokogiri.parse(xml)
This will work but assumes the first and last names are unique, otherwise it will modify the first match of first and last name.
target = doc.css('employee').find do |node|
node.search('firstname').text == 'Sandra' &&
node.search('lastname').text == 'Defoe'
end
target.at_css('building').content = '320'
target.at_css('room').content = '99'
doc # outputs the updated xml
=> <?xml version="1.0"?>
<?xml version="1.0" encoding="utf-16"?>
<employees>
<employee id="be129">
<firstname>Jane</firstname>
<lastname>Doe</lastname>
<building>327</building>
<room>19</room>
</employee>
<employee id="be130">
<firstname>William</firstname>
<lastname>Defoe</lastname>
<building>326</building>
<room>14a</room>
</employee>
<employee id="be132">
<firstname>Sandra</firstname>
<lastname>Defoe</lastname>
<building>320</building>
<room>99</room>
</employee>
<employee id="be133">
<firstname>Steve</firstname>
<lastname>Casey</lastname>
<building>327</building>
<room>24</room>
</employee>
</employees>

Xpath function to loop through repeating nodes

What XPath function works to loop through repeating XML nodes.
This is my Source XML:
<?xml version="1.0" encoding="UTF-8"?>
<Record>
<Type>V</Type>
<Address>
<Qual>A</Qual>
<ID>A1</ID>
</Address>
<Address>
<Qual>A</Qual>
<ID>B2</ID>
</Address>
<Address>
<Qual>C</Qual>
<ID>C2</ID>
</Address>
<Category>
<EL>PO</EL>
</Category>
<Category>
<EL>DP</EL>
</Category>
</Record>
I don't want to process the data if Qualf=A & ID = B2, Category =DP & Type =V
My Xpath does not work due to repeating nodes..
(concat(Xpath./Type,Xpath./Record/Address/Qual,Xpath./Record/Address/ID,Xpath./Record/Category/EL) != "VAB2DP"
so I tried
choose((concat(Xpath./Type,Xpath./Record/Address/Qual,Xpath./Record/Address/ID,Xpath./Record/Category/EL) != "VAB2DP"),'true','false'
It still does not work.

XSLT Sorting with xsl:sort doesn't work

The output xml doesn't sort as expected when I use the xsl:sort.
As you can see in the result section the 1.1.1.10, 2.10, 2.11, 2.12 are not correctly sorted.
I would expect the sorted lists to be as shown in the expected result xml.
Am I doing something wrong here? Please help.
Input XML
<children>
<child name="1.1.1.1 BDR Enter Customer" prefix="1.1.1.1"/>
<child name="1.1.1.10 BDR for Tax Office" prefix="1.1.1.10"/>
<child name="1.1.1.2 BDR Enter Customs" prefix="1.1.1.2"/>
<child name="1.1.1.3 BDR Enter Employee" prefix="1.1.1.3"/>
<child name="1.1.1.4 BDR Enter Forwarder" prefix="1.1.1.4"/>
<child name="1.1.1.5 BDR Enter Manufacturer" prefix="1.1.1.5"/>
<child name="1.1.1.6 BDR Enter Owner" prefix="1.1.1.6"/>
<child name="1.1.1.7 BDR Enter Person" prefix="1.1.1.7"/>
<child name="1.1.1.8 BDR Enter Resource" prefix="1.1.1.8"/>
<child name="1.1.1.9 BDR Enter Supplier" prefix="1.1.1.9"/>
<child name="1.1 Define System Basics" prefix="1.1"/>
<child name="2.9 Set Up Basic Data Accounting Rules" prefix="2.9"/>
<child name="2.2 Budget Management" prefix="2.2"/>
<child name="2.10 Customer Bill of Exchange Payment" prefix="2.10"/>
<child name="2.5 Depreciation Plan" prefix="2.5"/>
<child name="2.12 Supplier Credit Invoice" prefix="2.12"/>
<child name="2.11 AP/AR Nettings" prefix="2.11"/>
<child name="2.3 Arrival Entry Supplier Invoice" prefix="2.3"/>
<child name="2.1 Archive Data, Internal Ledger" prefix="2.1"/>
</children>
My XSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="children">
<children>
<xsl:for-each select="child">
<xsl:sort select="#prefix"/>
<child>
<xsl:attribute name="name">
<xsl:value-of select="#name"/>
</xsl:attribute>
<xsl:attribute name="prefix">
<xsl:value-of select="#prefix"/>
</xsl:attribute>
</child>
</xsl:for-each>
</children>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="utf-8"?>
<children>
<child name="1.1 Define System Basics" prefix="1.1"/>
<child name="1.1.1.1 BDR Enter Customer" prefix="1.1.1.1"/>
<child name="1.1.1.10 BDR for Tax Office" prefix="1.1.1.10"/>
<child name="1.1.1.2 BDR Enter Customs" prefix="1.1.1.2"/>
<child name="1.1.1.3 BDR Enter Employee" prefix="1.1.1.3"/>
<child name="1.1.1.4 BDR Enter Forwarder" prefix="1.1.1.4"/>
<child name="1.1.1.5 BDR Enter Manufacturer" prefix="1.1.1.5"/>
<child name="1.1.1.6 BDR Enter Owner" prefix="1.1.1.6"/>
<child name="1.1.1.7 BDR Enter Person" prefix="1.1.1.7"/>
<child name="1.1.1.8 BDR Enter Resource" prefix="1.1.1.8"/>
<child name="1.1.1.9 BDR Enter Supplier" prefix="1.1.1.9"/>
<child name="2.1 Archive Data, Internal Ledger" prefix="2.1"/>
<child name="2.10 Customer Bill of Exchange Payment" prefix="2.10"/>
<child name="2.11 AP/AR Nettings" prefix="2.11"/>
<child name="2.12 Supplier Credit Invoice" prefix="2.12"/>
<child name="2.2 Budget Management" prefix="2.2"/>
<child name="2.3 Arrival Entry Supplier Invoice" prefix="2.3"/>
<child name="2.5 Depreciation Plan" prefix="2.5"/>
<child name="2.9 Set Up Basic Data Accounting Rules" prefix="2.9"/>
</children>
Expected Result
<?xml version="1.0" encoding="utf-8"?>
<children>
<child name="1.1 Define System Basics" prefix="1.1"/>
<child name="1.1.1.1 BDR Enter Customer" prefix="1.1.1.1"/>
<child name="1.1.1.2 BDR Enter Customs" prefix="1.1.1.2"/>
<child name="1.1.1.3 BDR Enter Employee" prefix="1.1.1.3"/>
<child name="1.1.1.4 BDR Enter Forwarder" prefix="1.1.1.4"/>
<child name="1.1.1.5 BDR Enter Manufacturer" prefix="1.1.1.5"/>
<child name="1.1.1.6 BDR Enter Owner" prefix="1.1.1.6"/>
<child name="1.1.1.7 BDR Enter Person" prefix="1.1.1.7"/>
<child name="1.1.1.8 BDR Enter Resource" prefix="1.1.1.8"/>
<child name="1.1.1.9 BDR Enter Supplier" prefix="1.1.1.9"/>
<child name="1.1.1.10 BDR for Tax Office" prefix="1.1.1.10"/>
<child name="2.1 Archive Data, Internal Ledger" prefix="2.1"/>
<child name="2.2 Budget Management" prefix="2.2"/>
<child name="2.3 Arrival Entry Supplier Invoice" prefix="2.3"/>
<child name="2.5 Depreciation Plan" prefix="2.5"/>
<child name="2.9 Set Up Basic Data Accounting Rules" prefix="2.9"/>
<child name="2.10 Customer Bill of Exchange Payment" prefix="2.10"/>
<child name="2.11 AP/AR Nettings" prefix="2.11"/>
<child name="2.12 Supplier Credit Invoice" prefix="2.12"/>
</children>
Using XSLT 3.0 and the sort function from XPath 3.1 you could do it with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:template match="children">
<xsl:copy>
<xsl:apply-templates select="sort(child, (), function($c) { tokenize($c/#prefix, '\.')!xs:integer(.) })"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Saxon 9.7 PE or EE and recent versions of Altova XMLSpy or Raptor support XSLT 3.0.
Using Saxon 9 (including HE) you can do it with XSLT 2.0 and collation="http://saxon.sf.net/collation?alphanumeric=yes" on the xsl:sort:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="children">
<xsl:copy>
<xsl:apply-templates select="child">
<xsl:sort select="#prefix" collation="http://saxon.sf.net/collation?alphanumeric=yes"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Am I doing something wrong here?
Yes, you are doing two things wrong:
In the first place, you are sorting by a single sort key, when in fact you have four sort keys (at least) -- the separate segments of #prefix delimited by '.' characters.
In the second place, you appear to want a numeric sort by each key, but you are instead accepting the default lexicographic sort.
In XSLT, you can sort by multiple keys by providing multiple xsl:sort elements, and you can get numeric sorting via the data-type attributes of those elements. If you are restricted to XSLT 1.0 with no extensions, however, then that still leaves you with a slightly tricky problem of breaking up your prefix attribute into keys. For a fixed maximum number of segments, you can do it like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="children">
<xsl:copy>
<xsl:apply-templates select="child">
<xsl:sort data-type="number"
select="substring-before(#prefix, '.')"/>
<xsl:sort data-type="number"
select="substring-before(substring-after(concat(#prefix, '.0.0.0.'), '.'), '.')"/>
<xsl:sort data-type="number"
select="substring-before(substring-after(substring-after(concat(#prefix, '.0.0.0.'), '.'), '.'), '.')"/>
<xsl:sort data-type="number"
select="substring-before(substring-after(substring-after(substring-after(concat(#prefix, '.0.0.0.'), '.'), '.'), '.'), '.')"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note the use of the substring-before() and substring-after() functions to pick apart the prefix at the '.' delimiters, and the concatenation of '.0.0.0.' to the prefix to ensure that all prefixes are treated the same, regardless of depth, up to the maximum supported depth (four in this case, but that could be extended to whatever depth is needed).
Note also that xsl:copy is usually to be preferred over a literal output element, xsl:element, or xsl:attribute where the former is applicable, and that sorting can be applied to xsl:apply-templates, too, not just xsl:for-each.
If there really is no way to do this either by moving forward to a newer XSLT version or by exploiting vendor extensions, then the way I would tackle it is to preprocess the XML modifying the content from
<child name="1.1.1.10 BDR for Tax Office" prefix="1.1.1.10"/>
to
<child name="1.1.1.10 BDR for Tax Office" prefix="1.1.1.10"
k1="1" k2="1" k3="1" k4="10"/>
and then sort using a multi-part sort key:
<xsl:sort select="#k1" data-type="number"/>
<xsl:sort select="#k2" data-type="number"/>
<xsl:sort select="#k3" data-type="number"/>
<xsl:sort select="#k4" data-type="number"/>
The preprocessing can be done using a sequence of instructions such as
<xsl:attribute name="k2">
<xsl:value-of select="substring-before(substring-after(#prefix, '.'), '.')"/>
</xsl:attribute>

Handling an XML file with Ruby and Nokogiri

I am new to programming so bear with me. I have many XML documents that look like this:
File name: PRIDE_Exp_Complete_Ac_10094.xml.gz
<ExperimentCollection version="2.1">
<Experiment>
<ExperimentAccession>1015</ExperimentAccession>
<Title>Protein complexes in Saccharomyces cerevisiae (GPM06600002310)</Title>
<ShortLabel>GPM06600002310</ShortLabel>
<Protocol>
<ProtocolName>None</ProtocolName>
</Protocol>
<mzData version="1.05" accessionNumber="1015">
<cvLookup cvLabel="RESID" fullName="RESID Database of Protein Modifications" version="0.0" address="http://www.ebi.ac.uk/RESID/" />
<cvLookup cvLabel="UNIMOD" fullName="UNIMOD Protein Modifications for Mass Spectrometry" version="0.0" address="http://www.unimod.org/" />
<description>
<admin>
<sampleName>GPM06600002310</sampleName>
<sampleDescription comment="Ho, Y., et al., Systematic identification of protein complexes in Saccharomyces cerevisiae by mass spectrometry. Nature. 2002 Jan 10;415(6868):180-3.">
<cvParam cvLabel="NEWT" accession="4932" name="Saccharomyces cerevisiae (Baker's yeast)" value="Saccharomyces cerevisiae" />
</sampleDescription>
</admin>
</description>
<spectrumList count="0" />
</mzData>
</Experiment>
I want to take out the text in between "Title", "ProtocolName", and "SampleName" and save into a text file that has the same name as the .xml.gz. I have the following code so far (based on posts I saw on this site), but it seems not to work:
require 'rubygems'
require 'nokogiri'
doc = Nokogiri::XML(File.open("PRIDE_Exp_Complete_Ac_10094.xml.gz"))
#ExperimentCollection = doc.css("ExperimentCollection Title").map {|node| node.children.text }
Can someone help me?
Thanks
IF you are happy with REXML, AND there's only one <Experiment> per file, then something like the following should help ... (by the way, above text is invalid XML since no closing <ExperimentCollection> tag)
require "rexml/document"
include REXML
xml=<<EOD
<Experiment>
<ExperimentAccession>1015</ExperimentAccession>
<Title>Protein complexes in Saccharomyces cerevisiae (GPM06600002310)</Title>
<ShortLabel>GPM06600002310</ShortLabel>
<Protocol>
<ProtocolName>None</ProtocolName>
</Protocol>
<mzData version="1.05" accessionNumber="1015">
<cvLookup cvLabel="RESID" fullName="RESID Database of Protein Modifications" version="0.0" address="http://www.ebi.ac.uk/RESID/" />
<cvLookup cvLabel="UNIMOD" fullName="UNIMOD Protein Modifications for Mass Spectrometry" version="0.0" address="http://www.unimod.org/" />
<description>
<admin>
<sampleName>GPM06600002310</sampleName>
<sampleDescription comment="Ho, Y., et al., Systematic identification of protein complexes in Saccharomyces cerevisiae by mass spectrometry. Nature. 2002 Jan 10;415(6868):180-3.">
<cvParam cvLabel="NEWT" accession="4932" name="Saccharomyces cerevisiae (Baker's yeast)" value="Saccharomyces cerevisiae" />
</sampleDescription>
</admin>
</description>
<spectrumList count="0" />
</mzData>
</Experiment>
EOD
doc = Document.new xml
doc.elements["Experiment/Title"].text
doc.elements["Experiment/Protocol/ProtocolName"].text
doc.elements["Experiment/mzData/description/admin/sampleName"].text

ColdFusion loop through struct with key evaluation fails! What am i missing?

I have this code in my cfm, which works
<cfif not StructIsEmpty(form)>
<cfset larray = user.getArray() />
<cfloop collection="#form#" item="key">
<cfif left(key,4) eq "UPD_">
<cfset x = listLast(key,"_") />
<cfset y = evaluate(0,key) />
<cfloop index="j" from="1" to="#arrayLen(larray)#">
<cfif (larray[j][1] eq x) and (larray[j][3] neq y)>
<cfset larray[j][3] = y />
<cfif not LSIsNumeric(larray[j][3])>
<cfset larray[j][3] = "0" />
</cfif>
<cfset larray[j][4] = "update" />
</cfif>
</cfloop>
</cfif>
</cfloop>
<cfloop collection="#form#" item="key">
<cfif left(key,4) eq "DEL_">
<cfset x = listLast(key,"_") />
<cfloop index="k" from="1" to="#arrayLen(larray)#">
<cfif larray[k][1] eq x>
<cfset larray[k][4] = "delete" />
</cfif>
</cfloop>
</cfif>
</cfloop>
<cfset user = createObject("component", "cfc.User").init(
identifier = FormatBaseN(form.id,10),
array = larray
) />
</cfif>
<form name="usform" method="POST">
<cfset array = user.getArray() />
<cfoutput>
<cfloop index="i" from="1" to="#arrayLen(array)#">
<table>
<tr>
<td><input type="text" name="upd_#array[i][1]#" maxlength="6" size="6" value="#array[i][3]#" /></td>
<td><input type="checkbox" name="del_#array[i][1]#" /></td>
</tr>
</table>
<input type="hidden" name="id" value="#user.getIdentifier()#" />
</cfoutput>
</form>
I have put it into a cfc to seperate my logic and my view and i am trying to make it more generic
<cfcomponent name="ArrayManager" output="false">
<cffunction name="init" hint="constructor" output="false" returntype="ArrayManager">
<cfargument name="user" type="User" required="true" hint="User bean" />
<cfargument name="form" type="Struct" required="true" />
<cfset variables.instance.array = arguments.user.getArray() />
<cfset variables.instance.form = arguments.form />
<cfreturn this />
</cffunction>
<cffunction name="update" access="public" output="true" returntype="boolean">
<cfargument name="structstring" type="String" required="true" />
<cfargument name="seperator" type="String" required="true" />
<cfset var x = "0" />
<cfset var y = "0" />
<cfloop collection="#variables.instance.form#" item="key">
<cfif key eq "#arguments.structstring#">
<cfset x = listLast(key,"#arguments.seperator#") />
<cfset y = evaluate(0,key) />
<cfloop index="j" from="1" to="#arrayLen(variables.instance.array)#">
<cfif (variables.instance.array[j][1] eq x) and (variables.instance.array[j][3] neq y)>
<cfset variables.instance.array[j][3] = y />
<cfif not LSIsNumeric(variables.instance.array[j][3])>
<cfset variables.instance.array[j][3] = "0" />
</cfif>
<cfset variables.instance.array[j][4] = "update" />
</cfif>
</cfloop>
</cfif>
</cfloop>
<cfset arguments.user.init(array = variables.instance.array) />
<cfreturn true />
</cffunction>
<cffunction name="delete" access="public" output="false" returntype="boolean">
<cfargument name="structstring" type="String" required="true" />
<cfargument name="seperator" type="String" required="true" />
<cfset var x = "0" />
<cfloop collection="#variables.instance.form#" item="key">
<cfif key eq "#arguments.structstring#">
<cfset x = listLast(key,"#arguments.seperator#") />
<cfloop index="k" from="1" to="#arrayLen(variables.instance.array)#">
<cfif variables.instance.array[k][1] eq x>
<cfset variables.instance.array[k][4] = "delete" />
</cfif>
</cfloop>
</cfif>
</cfloop>
<cfset arguments.user.init(array = variables.instance.array) />
<cfreturn true />
</cffunction>
</cfcomponent>
And my new cfm
<cfif not StructIsEmpty(form)>
<cfset arraymanager = createObject("component","cfc.ArrayManager").init(user,form) />
<cfset seperator = "_" />
<cfset structstring = "UPD" />
<cfset arraymanager.update(structstring,seperator) />
</cfif>
...
It fails, i get this error message
The CFML compiler encountered an unexpected coldfusion.compiler.CompilerInternalException exception.
The reason for this was: Unable to complete CFML to Java translation. Occurred at:
.
.
.
The error occurred in C:\path\to\document\root\cfc\ArrayManager.cfc: line 21
Called from C:\path\to\document\root\cfc\update-emp.cfm: line 66
Called from C:\C:\path\to\document\root\cfc\update-emp.cfm: line 66
19 : <cfif key eq "#arguments.structstring#">
20:
21 : <cfset y = evaluate(0,key) />
22:
23 `:
What am i doing wrong or is there a better way to accomplish what i am trying to do (showing database content in a table and updating(Update and Delete) the database content through the same table)
The error message you've posted indicates that you're misusing the Evaluate function. According to the docs, it works like this:
Evaluates one or more string expressions, dynamically, from left to right. (The results of an evaluation on the left can have meaning in an expression to the right.) Returns the result of evaluating the rightmost expression.
But you've got other problems, too. For starters, you're not duplicating the logic correctly when you move the code into the CFC.
In your working code, you use the conditional:
<cfif left(key,4) eq "UPD_">
But in your CFC you have:
<cfif key eq arguments.structString>
This should be:
<cfif left(key,4) eq arguments.structString>
Next, you aren't using the best syntax for evaluate, and you probably don't want to use it at all. The statement:
y = evaluate(0,key)
can be rewritten as:
y = evaluate(key)
Since the value of key is "UPD_something", this can be rewritten as:
y = [variables|arguments|etc].UPD_Something
(Since you're not explicitly specifying a variable scope, CF will attempt to find the variable in a set of scopes in a certain order; which is why I used the syntax [a|b|...])
You probably don't mean this, you probably want the value from the form. And since the key name is dynamic, you should access it this way (instead of using evaluate):
y = variables.instance.form[key]
I think that may fix it. So, to summarize:
Replace your statement <cfif key eq arguments.structString> with <cfif left(key,4) eq arguments.structString> (And make sure that the value you pass as "structString" includes the underscore!)
Replace your use of evaluate with: y = variables.instance.form[key]
I hope that fixes your problems...
After you get it working, start thinking about your variable names. "Array" is a terrible variable name because it is practically a reserved word in CFML. And using "x" and "y" is not descriptive at all. These types of problems are what made this question hard to answer.
I fully agree to Adam Tuttle's post. I've removed the "solution part" part of my answer in favor of his. So here's my two cents regarding the "general part":
Avoiding Evaluate() altogether is the best thing you can do. There is no reason to use it apart from actually evaluating pieces of code (which is another bag of hurt that should be avoided). If that's not what you are doing, then there is no situation that could not be resolved through something more appropriate than Evaluate(), e.g.:
<cfset foo = Evaluate("FORM.#foo#")>
is equal to:
<cfset foo = FORM[foo]>
All of the "conveniency" misuses of Evaluate() can be addressed like this.
A few more tips:
Avoid meaningless variable names like "x" or "y" (unless you refer to 2D-coordinates, of course).
Don't do "#variable#" - you can simply use variable instead.
Use Structs wherever you can when you need to access items with a key, e.g.
<cfif array[i][1] eq SomeValue> is a lot less elegant than
<cfif array[i]["FieldName"] eq SomeValue>
You don't really need "variables.instance" - every component instance has it's own VARIABLES scope. Whatever you stick in there is "instance-only" by default.
No need to pass in the FORM scope to a component. The scope is global, so CFCs can see it anyway.
Why do you store database stuff in an extra array instead of using the Query object you got when you retrieved them?
No need to self-close ("/>") CFML statements - you are not writing XML (though that's a matter of taste).

Resources