从脚本读取 TDM (Diadem) 文件

发布于 2024-09-10 07:00:48 字数 360 浏览 3 评论 0原文

我的客户正在发送在 National Instruments Diadem 中捕获的 TDM/TDX 文件,但我没有收到。我正在寻找一种方法将文件转换为 .CSV、XLS 或 .MAT 文件,以便在 Matlab 中进行分析(不使用 Diadem 或 Diadem DLL!)

该格式由结构良好的 XML 文件 (.TDM) 和二进制文件 ( .TDX),.TDM 定义如何将字段打包为二进制 TDX 中的位。我想阅读这些文件(用于 Matlab 和其他环境)。有没有人有通用工具或转换脚本,例如Python或Perl(不使用NI DLL)或直接在Matlab中?

我考虑过购买该工具,但除了一次性转换为兼容的文件格式之外,我不喜欢它的任何其他功能。

谢谢!

My customer is sending TDM/TDX files captured in National Instruments Diadem, which I haven't got. I'm looking for a way to convert the files into .CSV, XLS or .MAT files for analysis in Matlab (without using Diadem or Diadem DLLs!)

The format consists of a well structured XML file (.TDM) and a binary (.TDX), with the .TDM defining how fields are packed as bits in the binary TDX. I'd like to read the files (for use in Matlab and other environments). Does anyone have a general purpose tool or conversion script in for instance Python or Perl (not using the NI DLL's) or directly in Matlab?

I've looked into buying the tool, but didn't like it for anything other than one-time conversion to a compatible file format.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

因为看清所以看轻 2024-09-17 07:00:48

我知道这有点晚了,但我有一个简单的库可以用 Python 读取 TDM/TDX 文件。它的工作原理是解析 TDM 文件以找出数据类型,然后使用 NumPy.memmap 打开 TDX 文件。然后可以像标准 NumPy 数组一样使用它。代码非常简单,因此您可以在 Matlab 中实现类似的东西。

这是链接:https://bitbucket.org/joshayers/tdm_loader

希望有所帮助。

I know this is a little late, but I have a simple library to read TDM/TDX files in Python. It works by parsing the TDM file to figure out the data type, then using NumPy.memmap to open the TDX file. It can then be used like a standard NumPy array. The code is pretty simple, so you could probably implement something similar in Matlab.

Here's the link: https://bitbucket.org/joshayers/tdm_loader

Hope that helps.

蓝眼睛不忧郁 2024-09-17 07:00:48

也许有点太晚了,但我认为有一种简单的方法可以从 TDM 文件中获取数据:NI 提供了用于将 TDM 文件读入 Excel 和 OpenOffice Calc 的插件。将数据存储在这些程序之一中后,您可以使用 CSV 导出。在 google 中搜索“tdm excel”或“tdm openoffice”。

希望这有帮助...
格穆埃

Maybe a little too late, but I think there is a simple way to get the data from TDM files: NI provides plug-ins for reading TDM files into Excel and OpenOffice Calc. Having the data in one of these programs you could use the CSV export. Search google for "tdm excel" or "tdm openoffice".

Hope this helps...
Gemue

殊姿 2024-09-17 07:00:48

以下脚本可以将所有变量转换为“变量”结构。

    CurrDirectory = '...//';  % Path to current directory
    fileNametdx = '.../utility/';  % Path to TDX file
    %%
    % Data type conversion 
    Dtype.eInt8Usi='int8';
    Dtype.eInt16Usi='int16';
    Dtype.eInt32Usi='int32';
    Dtype.eInt64Usi='int64';
    Dtype.eUInt8Usi='uint8';
    Dtype.eUInt16Usi='uint16';
    Dtype.eUInt32Usi='uint32';
    Dtype.eUInt64Usi='uint64';
    Dtype.eFloat32Usi='single';
    Dtype.eFloat64Usi='double';

    %% Read .tdx file Name
    wb=waitbar(0,'Reading *.tdx Files');
    fileNameTDM = strrep(fileNametdx,'.tdx','.TDM');

    %% Read .TDM
    tdm=xml2struct(fileNameTDM);

    for i=1:numel(tdm.usi_colon_tdm.usi_colon_data.tdm_channel)

        waitbar((1/numel(tdm.usi_colon_tdm.usi_colon_data.tdm_channel))*i,wb,['File ' fileNametdx ' conversion started']);
    s1=strsplit(string(tdm.usi_colon_tdm.usi_colon_data.tdm_channel{1, i}.local_columns.Text),'"');
    usi1=s1(2);

    % if condition match untill we get usi2
    for j=1:numel(tdm.usi_colon_tdm.usi_colon_data.localcolumn)
    usi2=string(tdm.usi_colon_tdm.usi_colon_data.localcolumn{1, j}.Attributes.id);
    if usi1==usi2
        %take new usi
        s2=strsplit(string(tdm.usi_colon_tdm.usi_colon_data.localcolumn{1, j}.values.Text),'"');
        new_usi1=s2(2);


        w1=strsplit(string(tdm.usi_colon_tdm.usi_colon_data.tdm_channel{1, i}.datatype.Text),'_');
        str_1=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence'));
        str_2=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.Attributes.id'));
        str_3=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.values.Attributes.external'));
        str_4=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.values'));
        for k=1:numel(eval(str_1))
        new_usi2=string(eval(str_2));
         if new_usi1==new_usi2
             if isfield(eval(str_4), 'Attributes')

             inc_value1=string(eval(str_3));

                    for m=1:numel(tdm.usi_colon_tdm.usi_colon_include.file.block)
                        inc_value2=string(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.id);
                        if inc_value1==inc_value2

        %                 offset=round(str2num(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.byteOffset)/8);
                        length = round(str2num(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.length));
                        offset1=round(str2num(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.byteOffset));
                        value_type = tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.valueType;

                        m = memmapfile(fullfile(CurrDirectory,fileNametdx),'Offset',offset1,'Format',{Dtype.(value_type) [length 1] 'dat'},'Writable',true,'Repeat',1);
                        dat=m.Data.dat  ;

                        end
                    end
             else

                 str_5=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.values.',char(fieldnames(tdm.usi_colon_tdm.usi_colon_data.string_sequence{1, k}.values))));
                 dat=eval(str_5)';
             end
                    name_variable = string(tdm.usi_colon_tdm.usi_colon_data.tdm_channel{1, i}.name.Text);
                    varname = genvarname(char(name_variable));
                    variable.(varname) = dat;
         end
        end
    end
    end
    end

    waitbar(1,wb,[fileNametdx ' conversion completed']);
    pause(1)
    close(wb)
    delete(fullfile(CurrDirectory,fileNametdx),fullfile(CurrDirectory,fileNameTDM));
    %Output Variable is Struct 
    clearvars -except variable

该脚本需要以下 XML 解析器

function [ s ] = xml2struct( file )
    %Convert xml file into a MATLAB structure
    % [ s ] = xml2struct( file )
    %
    % A file containing:
    % <XMLname attrib1="Some value">
    %   <Element>Some text</Element>
    %   <DifferentElement attrib2="2">Some more text</Element>
    %   <DifferentElement attrib3="2" attrib4="1">Even more text</DifferentElement>
    % </XMLname>
    %
    % Will produce:
    % s.XMLname.Attributes.attrib1 = "Some value";
    % s.XMLname.Element.Text = "Some text";
    % s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2";
    % s.XMLname.DifferentElement{1}.Text = "Some more text";
    % s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2";
    % s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1";
    % s.XMLname.DifferentElement{2}.Text = "Even more text";
    %
    % Please note that the following characters are substituted
    % '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_'
    %
    % Written by W. Falkena, ASTI, TUDelft, 21-08-2010
    % Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011
    % Added CDATA support by I. Smirnov, 20-3-2012
    %
    % Modified by X. Mo, University of Wisconsin, 12-5-2012

        if (nargin < 1)
            clc;
            help xml2struct
            return
        end

        if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl')
            % input is a java xml object
            xDoc = file;
        else
            %check for existance
            if (exist(file,'file') == 0)
                %Perhaps the xml extension was omitted from the file name. Add the
                %extension and try again.
                if (isempty(strfind(file,'.xml')))
                    file = [file '.xml'];
                end

                if (exist(file,'file') == 0)
                    error(['The file ' file ' could not be found']);
                end
            end
            %read the xml file
            xDoc = xmlread(file);
        end

        %parse xDoc into a MATLAB structure
        s = parseChildNodes(xDoc);

    end

    % ----- Subfunction parseChildNodes -----
    function [children,ptext,textflag] = parseChildNodes(theNode)
        % Recurse over node children.
        children = struct;
        ptext = struct; textflag = 'Text';
        if hasChildNodes(theNode)
            childNodes = getChildNodes(theNode);
            numChildNodes = getLength(childNodes);

            for count = 1:numChildNodes
                theChild = item(childNodes,count-1);
                [text,name,attr,childs,textflag] = getNodeData(theChild);

                if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section'))
                    %XML allows the same elements to be defined multiple times,
                    %put each in a different cell
                    if (isfield(children,name))
                        if (~iscell(children.(name)))
                            %put existsing element into cell format
                            children.(name) = {children.(name)};
                        end
                        index = length(children.(name))+1;
                        %add new element
                        children.(name){index} = childs;
                        if(~isempty(fieldnames(text)))
                            children.(name){index} = text; 
                        end
                        if(~isempty(attr)) 
                            children.(name){index}.('Attributes') = attr; 
                        end
                    else
                        %add previously unknown (new) element to the structure
                        children.(name) = childs;
                        if(~isempty(text) && ~isempty(fieldnames(text)))
                            children.(name) = text; 
                        end
                        if(~isempty(attr)) 
                            children.(name).('Attributes') = attr; 
                        end
                    end
                else
                    ptextflag = 'Text';
                    if (strcmp(name, '#cdata_dash_section'))
                        ptextflag = 'CDATA';
                    elseif (strcmp(name, '#comment'))
                        ptextflag = 'Comment';
                    end

                    %this is the text in an element (i.e., the parentNode) 
                    if (~isempty(regexprep(text.(textflag),'[\s]*','')))
                        if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag)))
                            ptext.(ptextflag) = text.(textflag);
                        else
                            %what to do when element data is as follows:
                            %<element>Text <!--Comment--> More text</element>

                            %put the text in different cells:
                            % if (~iscell(ptext)) ptext = {ptext}; end
                            % ptext{length(ptext)+1} = text;

                            %just append the text
                            ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)];
                        end
                    end
                end

            end
        end
    end

    % ----- Subfunction getNodeData -----
    function [text,name,attr,childs,textflag] = getNodeData(theNode)
        % Create structure of node info.

        %make sure name is allowed as structure name
        name = toCharArray(getNodeName(theNode))';
        name = strrep(name, '-', '_dash_');
        name = strrep(name, ':', '_colon_');
        name = strrep(name, '.', '_dot_');

        attr = parseAttributes(theNode);
        if (isempty(fieldnames(attr))) 
            attr = []; 
        end

        %parse child nodes
        [childs,text,textflag] = parseChildNodes(theNode);

        if (isempty(fieldnames(childs)) && isempty(fieldnames(text)))
            %get the data of any childless nodes
            % faster than if any(strcmp(methods(theNode), 'getData'))
            % no need to try-catch (?)
            % faster than text = char(getData(theNode));
            text.(textflag) = toCharArray(getTextContent(theNode))';
        end

    end

    % ----- Subfunction parseAttributes -----
    function attributes = parseAttributes(theNode)
        % Create attributes structure.

        attributes = struct;
        if hasAttributes(theNode)
           theAttributes = getAttributes(theNode);
           numAttributes = getLength(theAttributes);

           for count = 1:numAttributes
                %attrib = item(theAttributes,count-1);
                %attr_name = regexprep(char(getName(attrib)),'[-:.]','_');
                %attributes.(attr_name) = char(getValue(attrib));

                %Suggestion of Adrian Wanner
                str = toCharArray(toString(item(theAttributes,count-1)))';
                k = strfind(str,'='); 
                attr_name = str(1:(k(1)-1));
                attr_name = strrep(attr_name, '-', '_dash_');
                attr_name = strrep(attr_name, ':', '_colon_');
                attr_name = strrep(attr_name, '.', '_dot_');
                attributes.(attr_name) = str((k(1)+2):(end-1));
           end
        end
    end

The following script can convert all variables into 'variable' struct.

    CurrDirectory = '...//';  % Path to current directory
    fileNametdx = '.../utility/';  % Path to TDX file
    %%
    % Data type conversion 
    Dtype.eInt8Usi='int8';
    Dtype.eInt16Usi='int16';
    Dtype.eInt32Usi='int32';
    Dtype.eInt64Usi='int64';
    Dtype.eUInt8Usi='uint8';
    Dtype.eUInt16Usi='uint16';
    Dtype.eUInt32Usi='uint32';
    Dtype.eUInt64Usi='uint64';
    Dtype.eFloat32Usi='single';
    Dtype.eFloat64Usi='double';

    %% Read .tdx file Name
    wb=waitbar(0,'Reading *.tdx Files');
    fileNameTDM = strrep(fileNametdx,'.tdx','.TDM');

    %% Read .TDM
    tdm=xml2struct(fileNameTDM);

    for i=1:numel(tdm.usi_colon_tdm.usi_colon_data.tdm_channel)

        waitbar((1/numel(tdm.usi_colon_tdm.usi_colon_data.tdm_channel))*i,wb,['File ' fileNametdx ' conversion started']);
    s1=strsplit(string(tdm.usi_colon_tdm.usi_colon_data.tdm_channel{1, i}.local_columns.Text),'"');
    usi1=s1(2);

    % if condition match untill we get usi2
    for j=1:numel(tdm.usi_colon_tdm.usi_colon_data.localcolumn)
    usi2=string(tdm.usi_colon_tdm.usi_colon_data.localcolumn{1, j}.Attributes.id);
    if usi1==usi2
        %take new usi
        s2=strsplit(string(tdm.usi_colon_tdm.usi_colon_data.localcolumn{1, j}.values.Text),'"');
        new_usi1=s2(2);


        w1=strsplit(string(tdm.usi_colon_tdm.usi_colon_data.tdm_channel{1, i}.datatype.Text),'_');
        str_1=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence'));
        str_2=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.Attributes.id'));
        str_3=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.values.Attributes.external'));
        str_4=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.values'));
        for k=1:numel(eval(str_1))
        new_usi2=string(eval(str_2));
         if new_usi1==new_usi2
             if isfield(eval(str_4), 'Attributes')

             inc_value1=string(eval(str_3));

                    for m=1:numel(tdm.usi_colon_tdm.usi_colon_include.file.block)
                        inc_value2=string(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.id);
                        if inc_value1==inc_value2

        %                 offset=round(str2num(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.byteOffset)/8);
                        length = round(str2num(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.length));
                        offset1=round(str2num(tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.byteOffset));
                        value_type = tdm.usi_colon_tdm.usi_colon_include.file.block{1, m}.Attributes.valueType;

                        m = memmapfile(fullfile(CurrDirectory,fileNametdx),'Offset',offset1,'Format',{Dtype.(value_type) [length 1] 'dat'},'Writable',true,'Repeat',1);
                        dat=m.Data.dat  ;

                        end
                    end
             else

                 str_5=char(strcat('tdm.usi_colon_tdm.usi_colon_data.',lower(w1(2)),'_sequence{1, k}.values.',char(fieldnames(tdm.usi_colon_tdm.usi_colon_data.string_sequence{1, k}.values))));
                 dat=eval(str_5)';
             end
                    name_variable = string(tdm.usi_colon_tdm.usi_colon_data.tdm_channel{1, i}.name.Text);
                    varname = genvarname(char(name_variable));
                    variable.(varname) = dat;
         end
        end
    end
    end
    end

    waitbar(1,wb,[fileNametdx ' conversion completed']);
    pause(1)
    close(wb)
    delete(fullfile(CurrDirectory,fileNametdx),fullfile(CurrDirectory,fileNameTDM));
    %Output Variable is Struct 
    clearvars -except variable

This script requires following XML parser

function [ s ] = xml2struct( file )
    %Convert xml file into a MATLAB structure
    % [ s ] = xml2struct( file )
    %
    % A file containing:
    % <XMLname attrib1="Some value">
    %   <Element>Some text</Element>
    %   <DifferentElement attrib2="2">Some more text</Element>
    %   <DifferentElement attrib3="2" attrib4="1">Even more text</DifferentElement>
    % </XMLname>
    %
    % Will produce:
    % s.XMLname.Attributes.attrib1 = "Some value";
    % s.XMLname.Element.Text = "Some text";
    % s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2";
    % s.XMLname.DifferentElement{1}.Text = "Some more text";
    % s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2";
    % s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1";
    % s.XMLname.DifferentElement{2}.Text = "Even more text";
    %
    % Please note that the following characters are substituted
    % '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_'
    %
    % Written by W. Falkena, ASTI, TUDelft, 21-08-2010
    % Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011
    % Added CDATA support by I. Smirnov, 20-3-2012
    %
    % Modified by X. Mo, University of Wisconsin, 12-5-2012

        if (nargin < 1)
            clc;
            help xml2struct
            return
        end

        if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl')
            % input is a java xml object
            xDoc = file;
        else
            %check for existance
            if (exist(file,'file') == 0)
                %Perhaps the xml extension was omitted from the file name. Add the
                %extension and try again.
                if (isempty(strfind(file,'.xml')))
                    file = [file '.xml'];
                end

                if (exist(file,'file') == 0)
                    error(['The file ' file ' could not be found']);
                end
            end
            %read the xml file
            xDoc = xmlread(file);
        end

        %parse xDoc into a MATLAB structure
        s = parseChildNodes(xDoc);

    end

    % ----- Subfunction parseChildNodes -----
    function [children,ptext,textflag] = parseChildNodes(theNode)
        % Recurse over node children.
        children = struct;
        ptext = struct; textflag = 'Text';
        if hasChildNodes(theNode)
            childNodes = getChildNodes(theNode);
            numChildNodes = getLength(childNodes);

            for count = 1:numChildNodes
                theChild = item(childNodes,count-1);
                [text,name,attr,childs,textflag] = getNodeData(theChild);

                if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section'))
                    %XML allows the same elements to be defined multiple times,
                    %put each in a different cell
                    if (isfield(children,name))
                        if (~iscell(children.(name)))
                            %put existsing element into cell format
                            children.(name) = {children.(name)};
                        end
                        index = length(children.(name))+1;
                        %add new element
                        children.(name){index} = childs;
                        if(~isempty(fieldnames(text)))
                            children.(name){index} = text; 
                        end
                        if(~isempty(attr)) 
                            children.(name){index}.('Attributes') = attr; 
                        end
                    else
                        %add previously unknown (new) element to the structure
                        children.(name) = childs;
                        if(~isempty(text) && ~isempty(fieldnames(text)))
                            children.(name) = text; 
                        end
                        if(~isempty(attr)) 
                            children.(name).('Attributes') = attr; 
                        end
                    end
                else
                    ptextflag = 'Text';
                    if (strcmp(name, '#cdata_dash_section'))
                        ptextflag = 'CDATA';
                    elseif (strcmp(name, '#comment'))
                        ptextflag = 'Comment';
                    end

                    %this is the text in an element (i.e., the parentNode) 
                    if (~isempty(regexprep(text.(textflag),'[\s]*','')))
                        if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag)))
                            ptext.(ptextflag) = text.(textflag);
                        else
                            %what to do when element data is as follows:
                            %<element>Text <!--Comment--> More text</element>

                            %put the text in different cells:
                            % if (~iscell(ptext)) ptext = {ptext}; end
                            % ptext{length(ptext)+1} = text;

                            %just append the text
                            ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)];
                        end
                    end
                end

            end
        end
    end

    % ----- Subfunction getNodeData -----
    function [text,name,attr,childs,textflag] = getNodeData(theNode)
        % Create structure of node info.

        %make sure name is allowed as structure name
        name = toCharArray(getNodeName(theNode))';
        name = strrep(name, '-', '_dash_');
        name = strrep(name, ':', '_colon_');
        name = strrep(name, '.', '_dot_');

        attr = parseAttributes(theNode);
        if (isempty(fieldnames(attr))) 
            attr = []; 
        end

        %parse child nodes
        [childs,text,textflag] = parseChildNodes(theNode);

        if (isempty(fieldnames(childs)) && isempty(fieldnames(text)))
            %get the data of any childless nodes
            % faster than if any(strcmp(methods(theNode), 'getData'))
            % no need to try-catch (?)
            % faster than text = char(getData(theNode));
            text.(textflag) = toCharArray(getTextContent(theNode))';
        end

    end

    % ----- Subfunction parseAttributes -----
    function attributes = parseAttributes(theNode)
        % Create attributes structure.

        attributes = struct;
        if hasAttributes(theNode)
           theAttributes = getAttributes(theNode);
           numAttributes = getLength(theAttributes);

           for count = 1:numAttributes
                %attrib = item(theAttributes,count-1);
                %attr_name = regexprep(char(getName(attrib)),'[-:.]','_');
                %attributes.(attr_name) = char(getValue(attrib));

                %Suggestion of Adrian Wanner
                str = toCharArray(toString(item(theAttributes,count-1)))';
                k = strfind(str,'='); 
                attr_name = str(1:(k(1)-1));
                attr_name = strrep(attr_name, '-', '_dash_');
                attr_name = strrep(attr_name, ':', '_colon_');
                attr_name = strrep(attr_name, '.', '_dot_');
                attributes.(attr_name) = str((k(1)+2):(end-1));
           end
        end
    end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文