`
neeleon
  • 浏览: 181592 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

简捷强大的单文件XML操作工具类

    博客分类:
  • java
阅读更多

这个是XML操作工具类,只有一个类文件,使用的全部是JDK自带的类,简单易用。这个类主要包含了XML的读,写,验 证, 转换功能。这个类相比一些开源的XML解释工具(比如:JAXB, JiBX, Digester, Javolution, JDOM)好在,不用写任何配置文件,随到随用,非常方便。适合于项目中XML结构复杂,变化比较快,并且XML文件比较小的解释与生成。

源代码:
XmlUtils.java

package com.carver.commons.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.carver.commons.exception.XmlException;

/**
* Encapsulating XML common operations.
*
* @author carver
* @since 1.0, Jun 12, 2007
*/
public final class XmlUtils {

private static final String XMLNS_XSI = “xmlns:xsi”;
private static final String XSI_SCHEMA_LOCATION = “xsi:schemaLocation”;
private static final String LOGIC_YES = “yes”;
private static final String DEFAULT_ENCODE = “UTF-8″;
private static final String REG_INVALID_CHARS = “&#\\d+;”;

/**
* Creates a new document instance.
*
* @return a new document instance
* @throws XmlException problem creating a new document
*/
public static Document newDocument() throws XmlException {
Document doc = null;

try {
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.newDocument();
} catch (ParserConfigurationException e) {
throw new XmlException(e);
}

return doc;
}

/**
* Parses the content of the given XML file as an XML document.
*
* @param file the XML file instance
* @return the document instance representing the entire XML document
* @throws XmlException problem parsing the XML file
*/
public static Document getDocument(File file) throws XmlException {
InputStream in = getInputStream(file);
return getDocument(in);
}

/**
* Parses the content of the given stream as an XML document.
*
* @param in the XML file input stream
* @return the document instance representing the entire XML document
* @throws XmlException problem parsing the XML input stream
*/
public static Document getDocument(InputStream in) throws XmlException {
Document doc = null;

try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
doc = builder.parse(in);
} catch (ParserConfigurationException e) {
throw new XmlException(e);
} catch (SAXException e) {
throw new XmlException(XmlException.XML_PARSE_ERROR, e);
} catch (IOException e) {
throw new XmlException(XmlException.XML_READ_ERROR, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// nothing to do
}
}
}

return doc;
}

/**
* Creates a root element as well as a new document with specific tag name.
*
* @param tagName the name of the root element
* @return a new element instance
* @throws XmlException problem generating a new document
*/
public static Element createRootElement(String tagName) throws XmlException {
Document doc = newDocument();
Element root = doc.createElement(tagName);
doc.appendChild(root);
return root;
}

/**
* Gets the root element from input stream.
*
* @param in the XML file input stream
* @return the root element of parsed document
* @throws XmlException problem parsing the XML file input stream
*/
public static Element getRootElementFromStream(InputStream in)
throws XmlException {
return getDocument(in).getDocumentElement();
}

/**
* Gets the root element from given XML file.
*
* @param fileName the name of the XML file
* @return the root element of parsed document
* @throws XmlException problem parsing the XML file
*/
public static Element getRootElementFromFile(File file)
throws XmlException {
return getDocument(file).getDocumentElement();
}

/**
* Gets the root element from the given XML payload.
*
* @param payload the XML payload representing the XML file.
* @return the root element of parsed document
* @throws XmlException problem parsing the XML payload
*/
public static Element getRootElementFromString(String payload)
throws XmlException {
if (payload == null || payload.trim().length() < 1) {
throw new XmlException(XmlException.XML_PAYLOAD_EMPTY);
}

byte[] bytes = null;

try {
bytes = payload.getBytes(DEFAULT_ENCODE);
} catch (UnsupportedEncodingException e) {
throw new XmlException(XmlException.XML_ENCODE_ERROR, e);
}

InputStream in = new ByteArrayInputStream(bytes);
return getDocument(in).getDocumentElement();
}

/**
* Gets the descendant elements list from the parent element.
*
* @param parent the parent element in the element tree
* @param tagName the specified tag name
* @return the NOT NULL descendant elements list
*/
public static List<Element> getElements(Element parent, String tagName) {
NodeList nodes = parent.getElementsByTagName(tagName);
List<Element> elements = new ArrayList<Element>();

for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
elements.add((Element) node);
}
}

return elements;
}

/**
* Gets the immediately descendant element from the parent element.
*
* @param parent the parent element in the element tree
* @param tagName the specified tag name.
* @return immediately descendant element of parent element, NULL otherwise.
*/
public static Element getElement(Element parent, String tagName) {
List<Element> children = getElements(parent, tagName);

if (children.isEmpty()) {
return null;
} else {
return children.get(0);
}
}

/**
* Gets the immediately child elements list from the parent element.
*
* @param parent the parent element in the element tree
* @param tagName the specified tag name
* @return the NOT NULL immediately child elements list
*/
public static List<Element> getChildElements(Element parent, String tagName) {
NodeList nodes = parent.getElementsByTagName(tagName);
List<Element> elements = new ArrayList<Element>();

for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element && node.getParentNode() == parent) {
elements.add((Element) node);
}
}

return elements;
}

/**
* Gets the immediately child element from the parent element.
*
* @param parent the parent element in the element tree
* @param tagName the specified tag name
* @return immediately child element of parent element, NULL otherwise
*/
public static Element getChildElement(Element parent, String tagName) {
List<Element> children = getChildElements(parent, tagName);

if (children.isEmpty()) {
return null;
} else {
return children.get(0);
}
}

/**
* Gets the value of the child element by tag name under the given parent
* element. If there is more than one child element, return the value of the
* first one.
*
* @param parent the parent element
* @param tagName the tag name of the child element
* @return value of the first child element, NULL if tag not exists
*/
public static String getElementValue(Element parent, String tagName) {
String value = null;

Element element = getElement(parent, tagName);
if (element != null) {
value = element.getTextContent();
}

return value;
}

/**
* Appends the child element to the parent element.
*
* @param parent the parent element
* @param tagName the child element name
* @return the child element added to the parent element
*/
public static Element appendElement(Element parent, String tagName) {
Element child = parent.getOwnerDocument().createElement(tagName);
parent.appendChild(child);
return child;
}

/**
* Appends the child element as well as value to the parent element.
*
* @param parent the parent element
* @param tagName the child element name
* @param value the child element value
* @return the child element added to the parent element
*/
public static Element appendElement(Element parent, String tagName,
String value) {
Element child = appendElement(parent, tagName);
child.setTextContent(value);
return child;
}

/**
* Appends another element as a child element.
*
* @param parent the parent element
* @param child the child element to append
*/
public static void appendElement(Element parent, Element child) {
Node tmp = parent.getOwnerDocument().importNode(child, true);
parent.appendChild(tmp);
}

/**
* Appends the CDATA element to the parent element.
*
* @param parent the parent element
* @param tagName the CDATA element name
* @param value the CDATA element value
* @return the CDATA element added to the parent element
*/
public static Element appendCDATAElement(Element parent, String tagName,
String value) {
Element child = appendElement(parent, tagName);
if (value == null) { // avoid “null” word in the XML payload
value = “”;
}

Node cdata = child.getOwnerDocument().createCDATASection(value);
child.appendChild(cdata);
return child;
}

/**
* Converts the Node/Element instance to XML payload.
*
* @param node the node/element instance to convert
* @return the XML payload representing the node/element
* @throws XmlException problem converting XML to string
*/
public static String childNodeToString(Node node) throws XmlException {
String payload = null;

try {
Transformer tf = TransformerFactory.newInstance().newTransformer();

Properties props = tf.getOutputProperties();
props.setProperty(OutputKeys.OMIT_XML_DECLARATION, LOGIC_YES);
tf.setOutputProperties(props);

StringWriter writer = new StringWriter();
tf.transform(new DOMSource(node), new StreamResult(writer));
payload = writer.toString();
payload = payload.replaceAll(REG_INVALID_CHARS, ” “);
} catch (TransformerException e) {
throw new XmlException(XmlException.XML_TRANSFORM_ERROR, e);
}

return payload;
}

/**
* Converts the Node/Document/Element instance to XML payload.
*
* @param node the node/document/element instance to convert
* @return the XML payload representing the node/document/element
* @throws XmlException problem converting XML to string
*/
public static String nodeToString(Node node) throws XmlException {
String payload = null;

try {
Transformer tf = TransformerFactory.newInstance().newTransformer();

Properties props = tf.getOutputProperties();
props.setProperty(OutputKeys.INDENT, LOGIC_YES);
props.setProperty(OutputKeys.ENCODING, DEFAULT_ENCODE);
tf.setOutputProperties(props);

StringWriter writer = new StringWriter();
tf.transform(new DOMSource(node), new StreamResult(writer));
payload = writer.toString();
payload = payload.replaceAll(REG_INVALID_CHARS, ” “);
} catch (TransformerException e) {
throw new XmlException(XmlException.XML_TRANSFORM_ERROR, e);
}

return payload;
}

/**
* Converts the an XML file to XML payload.
*
* @param file the XML file instance
* @return the XML payload representing the XML file
* @throws XmlException problem transforming XML to string
*/
public static String xmlToString(File file) throws XmlException {
Element root = getRootElementFromFile(file);
return nodeToString(root);
}

/**
* Converts the an XML file input stream to XML payload.
*
* @param in the XML file input stream
* @return the payload represents the XML file
* @throws XmlException problem transforming XML to string
*/
public static String xmlToString(InputStream in) throws XmlException {
Element root = getRootElementFromStream(in);
return nodeToString(root);
}

/**
* Saves the node/document/element as XML file.
*
* @param doc the XML node/document/element to save
* @param file the XML file to save
* @throws XmlException problem persisting XML file
*/
public static void saveToXml(Node doc, File file) throws XmlException {
OutputStream out = null;

try {
Transformer tf = TransformerFactory.newInstance().newTransformer();

Properties props = tf.getOutputProperties();
props.setProperty(OutputKeys.METHOD, XMLConstants.XML_NS_PREFIX);
props.setProperty(OutputKeys.INDENT, LOGIC_YES);
tf.setOutputProperties(props);

DOMSource dom = new DOMSource(doc);
out = getOutputStream(file);
Result result = new StreamResult(out);
tf.transform(dom, result);
} catch (TransformerException e) {
throw new XmlException(XmlException.XML_TRANSFORM_ERROR, e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// nothing to do
}
}
}
}

/**
* Validates the element tree context via given XML schema file.
*
* @param doc the XML document to validate
* @param schemaFile the XML schema file instance
* @throws XmlException error occurs if the schema file not exists
*/
public static void validateXml(Node doc, File schemaFile)
throws XmlException {
validateXml(doc, getInputStream(schemaFile));
}

/**
* Validates the element tree context via given XML schema file.
*
* @param doc the XML document to validate
* @param schemaStream the XML schema file input stream
* @throws XmlException error occurs if validation fail
*/
public static void validateXml(Node doc, InputStream schemaStream)
throws XmlException {
try {
Source source = new StreamSource(schemaStream);
Schema schema = SchemaFactory.newInstance(
XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(source);

Validator validator = schema.newValidator();
validator.validate(new DOMSource(doc));
} catch (SAXException e) {
throw new XmlException(XmlException.XML_VALIDATE_ERROR, e);
} catch (IOException e) {
throw new XmlException(XmlException.XML_READ_ERROR, e);
} finally {
if (schemaStream != null) {
try {
schemaStream.close();
} catch (IOException e) {
// nothing to do
}
}
}
}

/**
* Transforms the XML content to XHTML/HTML format string with the XSL.
*
* @param payload the XML payload to convert
* @param xsltFile the XML stylesheet file
* @return the transformed XHTML/HTML format string
* @throws XmlException problem converting XML to HTML
*/
public static String xmlToHtml(String payload, File xsltFile)
throws XmlException {
String result = null;

try {
Source template = new StreamSource(xsltFile);
Transformer transformer = TransformerFactory.newInstance()
.newTransformer(template);

Properties props = transformer.getOutputProperties();
props.setProperty(OutputKeys.OMIT_XML_DECLARATION, LOGIC_YES);
transformer.setOutputProperties(props);

StreamSource source = new StreamSource(new StringReader(payload));
StreamResult sr = new StreamResult(new StringWriter());
transformer.transform(source, sr);

result = sr.getWriter().toString();
} catch (TransformerException e) {
throw new XmlException(XmlException.XML_TRANSFORM_ERROR, e);
}

return result;
}

/**
* Sets the namespace to specific element.
*
* @param element the element to set
* @param namespace the namespace to set
* @param schemaLocation the XML schema file location URI
*/
public static void setNamespace(Element element, String namespace,
String schemaLocation) {
element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
XMLConstants.XMLNS_ATTRIBUTE, namespace);
element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLNS_XSI,
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
element.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
XSI_SCHEMA_LOCATION, schemaLocation);
}

/**
* Encode the XML payload to legality character.
*
* @param payload the XML payload to encode
* @return the encoded XML payload
* @throws XmlException problem encoding the XML payload
*/
public static String encodeXml(String payload) throws XmlException {
Element root = createRootElement(XMLConstants.XML_NS_PREFIX);
root.setTextContent(payload);
return childNodeToString(root.getFirstChild());
}

private static InputStream getInputStream(File file) throws XmlException {
InputStream in = null;

try {
in = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new XmlException(XmlException.FILE_NOT_FOUND, e);
}

return in;
}

private static OutputStream getOutputStream(File file) throws XmlException {
OutputStream in = null;

try {
in = new FileOutputStream(file);
} catch (FileNotFoundException e) {
throw new XmlException(XmlException.FILE_NOT_FOUND, e);
}

return in;
}

}
 

XmlException.java

    package com.carver.commons.exception;

    /**
    * Runtime exception for XML handling.
    *
    * @author carver
    * @since 1.0, Jun 12, 2007
    */
    public class XmlException extends RuntimeException {

    private static final long serialVersionUID = 381260478228427716L;

    public static final String XML_PAYLOAD_EMPTY = “xml.payload.empty”;
    public static final String XML_ENCODE_ERROR = “xml.encoding.invalid”;
    public static final String FILE_NOT_FOUND = “xml.file.not.found”;
    public static final String XML_PARSE_ERROR = “xml.parse.error”;
    public static final String XML_READ_ERROR = “xml.read.error”;
    public static final String XML_VALIDATE_ERROR = “xml.validate.error”;
    public static final String XML_TRANSFORM_ERROR = “xml.transform.error”;

    public XmlException() {
    super();
    }

    public XmlException(String key, Throwable cause) {
    super(key, cause);
    }

    public XmlException(String key) {
    super(key);
    }

    public XmlException(Throwable cause) {
    super(cause);
    }

    }
 

使用示例:
user.xml

<user>
<id>100000</id>
<nick>carver.gu</nick>
<email>[email]carver.gu@gmail.com[/email]</email>
<gender>male</gender>
<contact>
<address>hangzhou</address>
<post-code>310019</post-code>
<telephone>88888888</telephone>
</contact>
</user>
 

 

解释XML:

    InputStream in = XmlTest.class.getResourceAsStream(“user.xml”);
    Element root = XmlUtils.getRootElementFromStream(in);
    String id = XmlUtils.getElementValue(root, “id”);
    String nick = XmlUtils.getElementValue(root, “nick”);
    String email = XmlUtils.getElementValue(root, “email”);
    String gender = XmlUtils.getElementValue(root, “gender”);

    Element contactE = XmlUtils.getChildElement(root, “contact”);
    String address = XmlUtils.getElementValue(contactE, “address”);
    String postCode = XmlUtils.getElementValue(contactE, “post-code”);
    String telephone = XmlUtils.getElementValue(contactE, “telephone”);
 

生成XML:

Element root = XmlUtils.createRootElement(“user”);
XmlUtils.appendElement(root, “id”, “100000″);
XmlUtils.appendElement(root, “nick”, “carver.gu”);
XmlUtils.appendElement(root, “email”, “[email]carver.gu@gmail.com[/email]“);
XmlUtils.appendElement(root, “gender”, “male”);

Element contactE = XmlUtils.appendElement(root, “contact”);
XmlUtils.appendElement(contactE, “address”, “hangzhou”);
XmlUtils.appendElement(contactE, “post-code”, “310019″);
XmlUtils.appendElement(contactE, “telephone”, “88888888″);

System.out.println(XmlUtils.nodeToString(root));
 
分享到:
评论

相关推荐

    程序员必备简捷开发辅助工具

    1. **Beyond Compare**:这是一款强大的文件和文本比较工具,能够快速准确地找出两个文件或目录之间的差异。在版本控制、代码合并、文件同步等场景下,Beyond Compare能帮助程序员高效地解决冲突问题,提升协同工作...

    飞鸽传书简捷版

    总的来说,"飞鸽传书简捷版"是一款强大的局域网通信工具,它的核心优势在于其无安装、易操作的特性以及丰富的辅助功能。结合IPMSG协议,它能提供安全、高效的文件和信息交换,是提升工作效率和团队协作能力的重要...

    虚拟光驱V2.0单文件版

    虚拟光驱是一款强大的计算机软件,它能够模拟物理光驱的功能,让用户无需物理光盘即可运行光盘镜像文件。在标题“虚拟光驱V2.0单文件版”中,关键词“V2.0”表明这是一个软件的特定版本,通常意味着它在功能和性能上...

    关闭winXP NT 2003局域网共享最简捷的批处理文件

    简捷方便的 关闭局域网共享的批处理文件! 操作方法:请执行批处理.cmd文件就OK! ====================== 中国电子爱好者论坛www.dzahz.net/bbs搜集整理,欢迎电子爱好者们前来查看更多资源。

    飞鸽传书(简捷版)v4.0

    总的来说,飞鸽传书(简捷版)v4.0以其高效、便捷的特性,成为日常工作、学习生活中不可或缺的文件传输工具。无论是个人还是团队,都能从中受益,享受到快速、安全的文件传输体验。而通过"飞鸽传书-简捷版说明.txt",...

    CAD简捷命令

    ### CAD简捷命令详解 #### 一、绘图命令 在CAD软件中,绘图是基本操作之一。以下是一些常用的绘图命令及其用途: - **直线(L)**:用于绘制直线段。 - **参照线(XL)**:创建无限长的构造线或参照线。 - **多线(ML)...

    Office 2003与XML

    讨论了XML与Office 2003的关系,检验了Office套件中各个不同的产品是如何创建并共享XML的,先讲述了XML的基本特征,然后提供了对导入信息到Office文档或将Offcie文件的信息导出到其他应用程序中的简捷、清晰的指导,...

    内网通讯飞鸽传书 单文件

    内网通讯飞鸽传书是一款专为局域网内的用户设计的即时通讯工具,它具有简单易用、高效快捷的特点,使得在企业内部进行文件传输和沟通变得极为方便。这款软件模仿了QQ等流行的即时通讯软件的操作模式,使得员工无需...

    Office 与XML(CHM)

    讨论了XML与Office的关系,检验了Office套件中各个不同的产品是如何创建并共享XML的,先讲述了XML的基本特征,然后提供了对导入信息到Office文档或将Offcie文件的信息导出到其他应用程序中的简捷、清晰的指导

    Office 2003与XML(CHM).rar

    讨论了XML与Office 2003的关系,检验了Office套件中各个不同的产品是如何创建并共享XML的,先讲述了XML的基本特征,然后提供了对导入信息到Office文档或将Offcie文件的信息导出到其他应用程序中的简捷、清晰的指导,...

    飞鸽传书简捷版 V4.0.120307 绿色版

    总的来说,飞鸽传书简捷版V4.0.120307绿色版是一个强大而实用的局域网通信工具,其文字消息、文件传输、语音通话以及群组通讯等功能设计都充分考虑到了用户的实际需求,为日常工作和生活提供了极大的便利。...

    趣味题与简捷解_李文汉-让你开窍的数学

    《趣味题与简捷解》中的PDF文件包含了全书的完整内容,包括各种数学问题、解析以及作者的精彩点评。读者可以逐页阅读,体验每一个问题的巧妙之处,同时也可以挑战自己,尝试在查看答案前独立解决。通过这样的互动式...

    SSH实现文件上传和下载

    文件上传和下载是 J2EE 编程中一个古老而重要的话题,SSH 框架提供了一个简捷方便的文件上传下载的方案,通过一些配置和少量的代码就可以完好解决这个问题。 总体实现 上传文件保存到 T_FILE 表中,T_FILE 表结构...

    XP一键优化,最简捷的一键操作完成对XP系统速度全面提升

    【描述】:在描述中提到,“最简捷的一键操作完成对XP系统速度全面提升”,这意味着该软件能够通过自动化流程,快速识别并优化系统中的各种设置,包括清理无用文件、关闭不必要的后台进程、优化启动项、修复注册表...

    winXP NT 2003硬盘各分区的默认共享打开和关闭最简捷的批处理文件

    硬盘各分区的默认共享-打开和关闭 操作方法:请执行批处理.cmd文件就OK! ====================== 中国电子爱好者论坛www.dzahz.net/bbs搜集整理,欢迎电子爱好者们前来查看更多资源。

    诗词格律简捷入门

    【诗词格律简捷入门】是一本专门为中华诗词研修班学员编写的教材,旨在让初学者快速理解并掌握诗词的格律知识。诗词格律是中国古代诗词创作的基础,它规定了诗词的音韵、对仗和押韵等规则,使得诗词在形式上具有独特...

    BDE快速安装 简捷实用

    5. **完成安装**:安装完成后,BDE的相关库文件和驱动程序会被添加到系统中,这样你的应用程序就可以调用BDE进行数据库操作了。 **BDE的简捷实用性** BDE之所以被称为简捷实用,主要体现在以下几个方面: 1. **...

    Stata基本操作和数据分析入门:第一讲 Stata操作入门.doc

    用户可以使用命令行方式直接建立数据集,或者使用数据编辑工具、打开已有数据文件和拷贝、粘贴方式交互数据。Stata 提供了简捷但是非常完善的数据接口,熟悉它的用法是使用 Stata 的第一步。 在 Stata 中读入数据...

    Aspen简捷法精馏塔设计计算实用教案.ppt

    对塔设备可分为三大类:简捷法计算的蒸馏塔、严格法计算的蒸馏塔和液-液萃取塔三类。 塔设备是化工生产中应用最为广泛的操作设备之一,通常在其中进行蒸馏、吸收和萃取单元操作。吸收和蒸馏实际都是气液相平衡的...

    飞鸽传书最新版本方便的局域网传送文件工具支持文件夹的传送

    是一款完全免费使用的局域网即时通讯工具!...3.软件操作简捷,界面清晰,任意拖曳即可实现操作。 4.自由沟通,自由群发,提高办公效率。 &gt;有很多创新的功能待您体验,赶快下载试用,感受网即通的与众不

Global site tag (gtag.js) - Google Analytics