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

时间:2018-07-30 15:10:07

标签: xml coldfusion

我正在一个函数中创建xml结构的基本轮廓,然后将用于创建xml内部/数据部分的数据传递到另一个函数中。如何使用Coldfusion将(或附加)添加到不同的xml结构中。我也不是很熟悉使用xml或Coldfusion,并且cf docs并没有真正帮助太多。

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

<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>

2 个答案:

答案 0 :(得分:3)

实际上,您想将一个XML文档插入某个位置的另一个XML文档中。

正确的 规范方法是以下步骤序列:

  1. 创建XML文档A(包装器)和B(要插入的数据)
  2. 从B中选择XML节点(列表)以插入到A中
  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 has also implemented an XmlAppend function


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

以下方法仅使用官方API,但在XML上使用正则表达式和字符串插值根本不是我所说的clean:

<!-- remove XML declaration if present -->
<cfset var dataXmlStr = REReplace(ToString(dataXml), '^<\?xml[^>]*>', '')>

<cfxml variable="wrapperXml">
    <Data><cfoutput>#dataXmlStr#</cfoutput></Data>
</cfxml>

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

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


我已经对其进行了测量,并且importNode / appendNode方法似乎比字符串插值方法更快。多少取决于实际任务。

答案 1 :(得分:1)

已更新02.07.18

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

功能:

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

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

<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#" />