当给定行和列或 AS3 中的索引时,如何在 XML 文档中获取节点?

How do I get the node in an XML document when given a row and column or an index in AS3?

我有一个文本区域,其中填充了 XML 文档的内容。我正在尝试获取用户将光标放置在其中的节点。

我可以很容易地得到行和列或者游标的索引。我还可以通过 new XML(textarea.text) 创建一个 XML 对象。

如果有XML.getNodeAtPosition(index)XML.getNodeAtPosition(row, column)方法就好了。

示例代码如下:

var row:int = 100;
var column:int = 10;
var xmlText:String = fileLoader.data as String;
textarea.text = xmlText;
textarea.setAnchor(row, column); // simulate user cursor
var xml:XML = new XML(textarea.text);
var node:XML = xml.getNodeAt(row, column);

注:
XML 可能已被用户编辑,可能无效 XML。我不认为 Flash 会创建一个 XML 对象,除非它是完全有效的,但如果可能的话就太好了。

Xml是一个类似xmlDoc[0][1][5][3]的树结构。一旦你这样想,你的问题就没有意义了。

我要使所选节点位于光标下的方法是递归地向后搜索文本区域中的文本以查找节点链名称<YourNodeName <YourParentNodeName <YourGrandParentNodeName,直到到达根。然后你可以通过 xml 和 e4x 像 xmlDoc.YourGrandParentNodeName.YourParentNodeName.YourNodeName 一样向下打点以获得节点。

Oook,我让这东西起作用了。首先,让我解释一下结果。您必须向此 class:

提供有效的 XML 字符串
var S:String = '<?xml version="123" ?><node b=\'"2\'><? oook ?><innernode a = "\'1" /><!-- 123 --><![CDATA[ 456 ]]>text</node>';
var A:ParXMLer =  new ParXMLer(S);

它同时遍历源字符串和结果 XML 并生成具有 begin/end 文本索引的切片列表、表示切片的源文本的一部分和对适当 [=26= 的引用] 节点(是的,前导 子句被忽略,因为它不会转到 XML 对象):

[NODE 22:107] <node b='"2'><? oook ?><innernode a = "'1" /><!-- 123 --><![CDATA[ 456 ]]>text</node>
[HEAD 22:35] <node b='"2'>
[PI 35:45] <? oook ?>
[NODE 45:67] <innernode a = "'1" />
[COMMENT 67:79] <!-- 123 -->
[CDATA 79:96] <![CDATA[ 456 ]]>
[TEXT 96:100] text
[TAIL 100:107] </node>

然后,class。使用方法 .nodeByIndex(index:int):XML 获取对最相关 XML 节点的引用。

package
{
    public class ParXMLer
    {
        public var targetXML:XML;
        public var parseIndex:int;
        public var sourceText:String;

        public var slices:Vector.<XMLSlice> = new Vector.<XMLSlice>();

        public function ParXMLer(source:String, target:XML = null)
        {
            XML.ignoreComments = false;
            XML.ignoreProcessingInstructions = false;

            try
            {
                if (target == null) target = new XML(source);
            }
            catch (fail:Error)
            {
                trace(fail);
            }

            parseIndex = 0;
            targetXML = target;
            sourceText = source;

            parseNode(targetXML);
        }

        // Obtain the most relevant XML node by its source string index.
        public function nodeByIndex(index:int):XML
        {
            var result:XMLSlice;

            for each (var aSlice:XMLSlice in slices)
            {
                if (aSlice.begin > index) continue;
                if (aSlice.end <= index) continue;

                if (result == null)
                {
                    result = aSlice;
                }
                else if (aSlice.begin > result.begin)
                {
                    result = aSlice;
                }
            }

            if (result == null) return null;

            return result.node;
        }

        public function toString():String
        {
            return slices.join("\n");
        }

        // Figure the given node type and parse it accordingly.
        private function parseNode(X:XML):void
        {
            var aKind:String = X.nodeKind();

            switch (aKind)
            {
                case "element":
                    parseElement(X);
                    break;

                case "text":
                    parseText(X);
                    break;

                case "comment":
                    parseComment(X);
                    break;

                case "processing-instruction":
                    parsePi(X);
                    break;
            }
        }

        // Parse normal XML node.
        private function parseElement(X:XML):void
        {
            var aHead:XMLSlice = parseHead(X);

            if (aHead.type == XMLSlice.WHOLE)
            {
                slices.push(aHead);
                return;
            }

            var result:XMLSlice = new XMLSlice();

            result.node = X;
            result.begin = aHead.begin;
            result.type = XMLSlice.WHOLE;

            slices.push(result, aHead);

            parseKids(X);

            var aTail:XMLSlice = parseTail(X);

            slices.push(aTail);

            result.end = aTail.end;
            result.text = sourceText.substring(result.begin, result.end);
        }

        // Parse </close> tailing tag.
        private function parseTail(X:XML):XMLSlice
        {
            var result:XMLSlice = new XMLSlice();

            result.node = X;
            result.type = XMLSlice.TAIL;
            result.begin = sourceText.indexOf("</", parseIndex);

            parseIndex = result.begin + 2;

            result.end = sourceText.indexOf(">", parseIndex) + 1;

            parseIndex = result.end;

            result.text = sourceText.substring(result.begin, result.end);

            return result;
        }

        // Parse XML node children.
        private function parseKids(X:XML):void
        {
            var aList:XMLList = X.children();

            for (var i:int = 0; i < aList.length(); i++)
            {
                var aChild:XML = aList[i];
                parseNode(aChild);
            }
        }

        // Parse XML node <open ... > tag.
        private function parseHead(X:XML):XMLSlice
        {
            var result:XMLSlice = new XMLSlice();
            var aTag:String = "<" + X.name();

            result.node = X;
            result.begin = sourceText.indexOf(aTag, parseIndex);

            parseIndex = result.begin + aTag.length;

            var aClause:XMLClause = avoidQuotes("/>", ">");

            result.end = aClause.index + aClause.text.length;
            result.text = sourceText.substring(result.begin, result.end);

            switch (aClause.text)
            {
                case "/>":
                    result.type = XMLSlice.WHOLE;
                    break;

                case ">":
                    result.type = XMLSlice.HEAD;
                    break;
            }

            return result;
        }

        // Search for the foremost occurrence of ANY of the given arguments.
        private function search(...rest:Array):XMLClause
        {
            var result:XMLClause = new XMLClause();

            for each (var anItem:String in rest)
            {
                var anIndex:int = sourceText.indexOf(anItem, parseIndex);

                if (anIndex < 0) continue;
                if (anIndex < result.index)
                {
                    result.index = anIndex;
                    result.text = anItem;
                }
            }

            return result;
        }

        // Search for matching quote with regard to "\"" and '\'' cases.
        private function unquote(quote:String):void
        {
            while (true)
            {
                var aClause:XMLClause = search("\" + quote, quote);

                parseIndex = aClause.index + aClause.text.length;
                if (aClause.text == quote) return;
            }
        }

        // Find the end of the tag avoiding text in the quotes.
        private function avoidQuotes(...rest:Array):XMLClause
        {
            var aList:Array = ['"', "'"].concat(rest);

            while (true)
            {
                var result:XMLClause = search.apply(this, aList);

                switch (result.text)
                {
                    case '"':
                    case "'":
                        unquote(result.text);
                        break;

                    default:
                        return result;
                }
            }

            return null;
        }

        // Parse <? ... ?> tag.
        private function parsePi(X:XML):void
        {
            var result:XMLSlice = new XMLSlice();

            result.node = X;
            result.type = XMLSlice.PI;
            result.begin = sourceText.indexOf("<?", parseIndex);

            parseIndex = result.begin + 2;

            var aClause:XMLClause = avoidQuotes("?>");

            result.end = aClause.index + 2;
            result.text = sourceText.substring(result.begin, result.end);

            slices.push(result);
        }

        // Parse <!-- ... --> tag.
        private function parseComment(X:XML):void
        {
            var result:XMLSlice = new XMLSlice();

            result.node = X;
            result.type = XMLSlice.COMMENT;
            result.begin = sourceText.indexOf("<!--", parseIndex);
            result.end = sourceText.indexOf("-->", result.begin) + 3;
            result.text = sourceText.substring(result.begin, result.end);

            parseIndex = result.end;
            slices.push(result);
        }

        static private const SPACES:String = " \n\r\t";

        private function eatWhitespaces():void
        {
            while (SPACES.indexOf(sourceText.charAt(parseIndex)) > -1) parseIndex++;
        }

        // Parse plain text tag or <![CDATA[ ... ]]> tag.
        private function parseText(X:XML):void
        {
            eatWhitespaces();

            var result:XMLSlice = new XMLSlice();

            if (sourceText.indexOf("<![CDATA[", parseIndex) == parseIndex)
            {
                result.type = XMLSlice.CDATA;
                result.begin = sourceText.indexOf("<![CDATA[", parseIndex);
                result.end = sourceText.indexOf("]]>", result.begin) + 3;
            }
            else
            {
                result.type = XMLSlice.TEXT;
                result.begin = parseIndex;
                result.end = sourceText.indexOf("<", parseIndex);
            }

            result.node = X;
            result.text = sourceText.substring(result.begin, result.end);

            parseIndex = result.end;
            slices.push(result);
        }
    }
}

internal class XMLSlice
{
    static public const COMMENT:String = "COMMENT";
    static public const CDATA:String = "CDATA";
    static public const TEXT:String = "TEXT";
    static public const PI:String = "PI";
    static public const WHOLE:String = "NODE";
    static public const HEAD:String = "HEAD";
    static public const TAIL:String = "TAIL";

    public var type:String;
    public var begin:int;
    public var end:int;
    public var node:XML;
    public var text:String;

    public function get length():int { return text.length; }
    public function toString():String { return "[" + type + " " + begin + ":" + end + "] " + text; }
}

internal class XMLClause
{
    public var index:int = int.MAX_VALUE;
    public var text:String = null;
}