如何在另一个XML结构中构建XML结构



我在一个函数中创建一个xml结构的基本轮廓,然后将用于创建xml内部/数据部分的数据传递到另一个函数。如何使用coldfusion向不同的xml结构添加(或附加(。我也不太熟悉使用xml或coldfusion,而且cf文档也没有太大帮助。

这是我所做事情的简化版本,但它说明了这一点:

<cffunction  name="getSomeXML" access="private">
<cfargument  name="qryToGetData" type="query">
<cfset var LOCAL = StructNew()>
<cfsavecontent  variable="LOCAL.XML"><?xml version="1.0" encoding="utf-8"?>
<Types>
<cfoutput query="qryToGetData">
<A><![CDATA[#xmlFormat(qryToGetData.aType)#]]></A>
<B><![CDATA[#xmlFormat(qryToGetData.bType)#]]></B>
<C><![CDATA[#xmlFormat(qryToGetData.cType)#]]></C>
<D><![CDATA[#xmlFormat(qryToGetData.dType)#]]></D>
</cfoutput>
</Types>
</cfsavecontent>
<cfset LOCAL = createInnerXML(LOCAL)> <!--- Call Function to Make Inner XML --->
<cfreturn LOCAL> <!--- Return Complete XML --->
</cffunction>
<cffunction  name="innerXML" access="private">
<cfargument name="arguments" hint="arguments">
<cfset var innerXMLSRT = StructNew()>
<cfsavecontent  variable="innerXMLSRT.XML"><?xml version="1.0" encoding="utf-8"?>
<Data>
<cfoutput>
#arguments.xmlFeed#
</cfoutput>
</Data>
</cfsavecontent>
<cfreturn innerXMLSRT> <!--- Return Complete XML --->
</cffunction>
<!--- OUTPUT: --->
<Types>
<Data>
<A></A>
<B></B>
<C></C>
<D></D>
</Data>
</Types>

实际上,您希望在某个位置将一个XML文档插入到另一个文档中。

正确的标准方法是以下步骤序列:

  1. 创建XML文档A(包装器(和B(要插入的数据(
  2. 从B中挑选要插入到a中的XML节点
  3. 对于每个这样的节点,导入A
  4. 对于每个这样导入的节点,将其插入所需位置的A中

这种相当复杂的方法是必要的,因为XML节点不能在不同的DOM树之间自由移动。每个节点都有一个"所有者文档",在节点的生存期内不能更改。创建物理副本是将节点放入不同文档的唯一方法。

然而,在ColdFusion中还有一个额外的复杂性。它支持一些非常方便的语法来处理XML文档。XML对象的行为既像数组,也像结构,并使用相关函数,您可以在代码中直接使用点路径(如xml.Data.Foo.XmlAttributes.Bar(来读取例如属性值。这种便利程度是有代价的——ColdFusion将XML节点封装到抽象层中,从而隐藏底层的实际节点对象,这总体上使访问节点导入等所需的DOM方法变得困难。

在深入研究了ColdFusion的XML内部之后,我提出了一个折中的优雅函数:

<cffunction name="XmlAppend" returntype="void" output="no">
<cfargument name="target" type="xml" required="yes">
<cfargument name="source" type="array" required="yes">
<cfset var sNode = "">
<cfloop array="#source#" index="sNode">
<cfset target.appendChild(
target.getOwnerDocument().importNode(sNode.cloneNode(true), true)
)>
</cfloop>
</cffunction>

此函数可以将任意数量的source节点复制到一个target节点。由于函数在适当的位置修改目标节点,因此没有返回值。

关键部分是对.cloneNode()的调用。.importNode()函数无法处理sNode实际是的ColdFusion XML包装对象(类:coldfusion.xml.XmlNodeList,包装org.apache.xerces.dom.DeferredElementNSImpl和表兄弟(。

(我发现(没有办法从包装器中获得底层的XML节点对象,除非通过克隆它。所以这一步很浪费,但没有其他方法可以让.importNode()发挥作用。

有了这些XML文档,

<cfxml variable="dataXml">
<Types>
<a>aaaa</a>
<a>bbbb</a>
</Types>
</cfxml>
<cfxml variable="wrapperXml">
<Data>
<some>exsting node</some>
</Data>
</cfxml>

呼叫工作方式如下:

<!-- all children of /Types into the /Data node -->
<cfset XmlAppend(wrapperXml.Data, dataXml.Types.XmlChildren)>
<!-- same thing, but with XPath and explicit argument names -->
<cfset XmlAppend(
target: XmlSearch(wrapperXml, '/Data')[1],
source: XmlSearch(dataXml, '/Types/*')
)>

结果是:

<Data>
<some>exsting node</some>
<a>aaaa</a>
<a>bbbb</a>
</Data>

此函数使用了大量未记录的API。我已经从CF 7到CF 2016对它进行了测试,它在所有这些版本中都有效,但当你将它放入生产代码中,然后ColdFusion的未来更新会破坏它,或者当它在Railo等第三方实现中根本不起作用时,你仍然会独自一人。使用后果自负。

为了完整起见,Ben Nadel还实现了一个XmlAppend函数。


仅使用官方支持的API,此任务无法在ColdFusion中以nice的方式完成。

以下方法只使用官方的API,但在XML上使用regex和字符串插值根本不是我所说的干净:

<!-- remove XML declaration if present -->
<cfset var dataXmlStr = REReplace(ToString(dataXml), '^<?xml[^>]*>', '')>
<cfxml variable="wrapperXml">
<Data><cfoutput>#dataXmlStr#</cfoutput></Data>
</cfxml>

删除XML声明是至关重要的,因为当我们对XML对象调用ToString()时,ColdFusion总是会添加它,但让它出现在XML文档中的任何位置都是不合法的,除非在一开始就出现一次。忘记删除XML声明将导致<cfxml>中的解析器错误。

请注意,<cfxml>实际上与<cfsavecontent>相同——它从主体创建一个字符串,但作为最后一步,它还将该字符串解析为XML文档。


我已经测量过了,importNode/appendNode方法似乎通常比字符串插值方法更快。多少取决于实际任务。

更新日期:2018年7月2日

好的。在Ben Nadel的AppendXml((函数的基础上,我为您的需求创建了一个自定义函数,该函数将使用纯XML方法附加到现有节点。

功能:

  • 此解决方案使用官方记录的Coldfusion函数
  • 返回值可以是字符串或对象
  • 输入字符串可以是片段,也可以使用prolog

如果列名不同,只需将查询列引用更改为新的。

<cffunction name="AppendXml" returntype="any" output="no" access="private">
<!---arguments--->
<cfargument name="query" type="query" required="yes" hint="I output the CDATA">
<cfargument name="xml" type="any" required="yes" hint="I am the orginal XML text string that requires appending">
<cfargument name="outputString" type="boolean" required="no" default="true" hint="I output whether the XML is in object or string format">
<!---local variables--->
<cfset var local = StructNew()>
<!---logic--->
<cfset local.xml = REReplaceNoCase(ToString(arguments.xml),"<?[^>]*>","")>
<cfif IsXml(local.xml)>
<cfset local.xml = XmlParse(local.xml)>
<cfset ArrayAppend(local.xml.Data.XmlChildren,XmlElemNew(local.xml,"Types"))>
<cfloop query="arguments.query">
<cfset ArrayAppend(local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)].XmlChildren,XmlElemNew(local.xml,"A"))>
<cfset local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)]["A"].XmlCData = xmlFormat("aType")>
<cfset ArrayAppend(local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)].XmlChildren,XmlElemNew(local.xml,"B"))>
<cfset local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)]["B"].XmlCData = xmlFormat("bType")>
<cfset ArrayAppend(local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)].XmlChildren,XmlElemNew(local.xml,"C"))>
<cfset local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)]["C"].XmlCData = xmlFormat("cType")>
<cfset ArrayAppend(local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)].XmlChildren,XmlElemNew(local.xml,"D"))>
<cfset local.xml.Data.Types[ArrayLen(local.xml.Data.XmlChildren)]["D"].XmlCData = xmlFormat("dType")>
</cfloop>
<cfif arguments.outputString>
<cfset local.xml = ToString(local.xml)>
</cfif>
<cfelse>
<cfset local.xml = arguments.xml>
</cfif>
<cfreturn local.xml>
</cffunction>
<cfset query = QueryNew("aType,bType,cType,dType")>
<cfset QueryAddRow(query)> 
<cfset QuerySetCell(query,"aType","small")>
<cfset QuerySetCell(query,"bType","medium")>
<cfset QuerySetCell(query,"cType","large")>
<cfset QuerySetCell(query,"dType","extra large")>
<cfsavecontent variable="xml">
<?xml version="1.0" encoding="utf-8"?>
<Data>
<Types>
<A><![CDATA[small]]></A>
<B><![CDATA[medium]]></B>
<C><![CDATA[large]]></C>
<D><![CDATA[extralarge]]></D>
</Types>
<Types>
<A><![CDATA[small]]></A>
<B><![CDATA[medium]]></B>
<C><![CDATA[large]]></C>
<D><![CDATA[extralarge]]></D>
</Types>
</Data> 
</cfsavecontent>
<cfset AppendXml = AppendXml(query=query,xml=xml,outputString=false)>
<cfdump var="#AppendXml#" />

最新更新