`

Dom4j节点处理器的实现

阅读更多
Dom4j节点处理器的实现

Dom4j为XML文档解析提供了强大的API支持,在实际开发中,为了处理方便,常常以节点元素(Element)为单位进行处理,并且结合数据库和Java技术,为节点生成ID属性。这样,就很有必要在Dom4j的基础上,重新封装一些适用需要的方法,以提高开发效率。下面是我利用业余时间做的一个Dom4j节点处理器工具类的实现。希望能节省开发人员宝贵时间。

说明:为了写起来方便,可以单独运行,我没有写单独的测试类,实际用的时候,删除掉测试main()和test()方法即可。

注释很详细,不用废话了。下面是处理器的实现:

测试用的xml:
输出三个测试对象
----------srcXml------------
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            <add ID="10001">
                <BS>10001</BS>
                <note>郑州市经三路</note>
            </add>
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            <add ID="">
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
            <add>
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
        </adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>
----------nodeXml1------------
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            <add ID="10001">
                <BS>10001</BS>
                <note>郑州市经三路</note>
            </add>
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            <add ID="">
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
            <add>
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
        </adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>
----------nodeXml2------------
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            <add ID="10001">
                <BS>10001</BS>
                <note>郑州市经三路</note>
            </add>
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            <add ID="">
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
            <add>
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
        </adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>

处理器的实现类:
package com.topsoft.icib.common.utils;

import org.dom4j.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.StringUtils;

import java.util.Random;
import java.util.Iterator;
import java.util.ArrayList;

/**
* Created by IntelliJ IDEA.<br>
* <b>User</b>: leizhimin<br>
* <b>Date</b>: 2008-3-27 18:42:39<br>
* <b>Note</b>: XML节点处理器,包含XML元素的CRUD方法。
*/
public class XmlNodeHandler {
    private static Log log = LogFactory.getLog(XmlNodeHandler.class);

    /**
     * 在xml的指定位置插入一个元素
     *
     * @param srcXml  原xml
     * @param nodeXml 元素xml
     * @param xpath   要插入元素父节点的位置
     * @return 原xml插入节点后的完整xml文档
     */
    public static String addElement(String srcXml, String nodeXml, String xpath) {
        String resultXml = null;
        try {
            Document docSrc = DocumentHelper.parseText(srcXml);
            Document docNode = DocumentHelper.parseText(nodeXml);
            Element parentElement = (Element) docSrc.getRootElement().selectSingleNode(xpath);
            parentElement.add(docNode.getRootElement());
            resultXml = docSrc.asXML();
        } catch (DocumentException e) {
            log.error("在文档" + xpath + "位置添加新节点发生异常,请检查!");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }

    /**
     * 删除xml文档中指定ID的元素
     *
     * @param srcXml    原xml
     * @param xmlNodeId 元素ID属性值
     * @return 删除元素后的xml文档
     */
    public static String removeElementById(String srcXml, String xmlNodeId) {
        String resultXml = null;
        try {
            Document docSrc = DocumentHelper.parseText(srcXml);
            Element removeElement = docSrc.getRootElement().elementByID(xmlNodeId);
            removeElement.detach();  //直接删除自己
//            removeElement.getParent().remove(removeElement);  //从父节点删除子节点
            resultXml = docSrc.asXML();
        } catch (DocumentException e) {
            log.error("删除文档中ID为" + xmlNodeId + "的节点发生异常,请检查!");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }

    /**
     * 删除xml文档中以xpath为直接父节点的ID属性为空的子节点,ID属性为空包括值为空、空串、或者ID属性不存在。
     *
     * @param srcXml 原xml文档
     * @param xpath  要删除空节点的所在父节点的xpath
     * @return 删除空节点后的xml文档
     */
    public static String removeNullIdElement(String srcXml, String xpath) {
        String resultXml = null;
        try {
            Document srcDoc = DocumentHelper.parseText(srcXml);
            removeNullIdElement(srcDoc, xpath);
            resultXml = srcDoc.asXML();
        } catch (DocumentException e) {
            log.error("在" + xpath + "下删除空节点发生异常,请检查xpath是否正确!");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }

    /**
     * 删除xml文档中以xpath为直接父节点的ID属性为空的子节点,ID属性为空包括值为空、空串、或者ID属性不存在。
     *
     * @param srcDoc 原xml的Document对象
     * @param xpath  要删除空节点的所在父节点的xpath
     * @return 删除空节点后的xml文档
     */
    public static Document removeNullIdElement(Document srcDoc, String xpath) {
        Node parentNode = srcDoc.getRootElement().selectSingleNode(xpath);
        if (!(parentNode instanceof Element)) {
            log.error("所传入的xpath不是Elementpath,删除空节点失败!");
        } else {
            int i = 0;
            for (Iterator<Element> it = ((Element) parentNode).elementIterator(); it.hasNext();) {
                Element element = it.next();
                if (element.attribute("ID") == null) {
                    element.detach();
                    i++;
                } else {
                    if (StringUtils.isBlank(element.attribute("ID").getValue())) {            
                            element.detach();
                            i++;
                     }
                }
            }
            log.info("在" + xpath + "下成功删除" + i + "了个空节点!");
        }
        return srcDoc;
    }

    /**
     * 删除xml文档中指定xpath路径下所有直接子节点为空的节点
     *
     * @param srcXml    原xml文档
     * @param xpathList xpaht列表
     * @return 删除空节点后的xml文档
     */
    public static String removeAllNullIdElement(String srcXml, ArrayList<String> xpathList) {
        String resultXml = null;
        try {
            Document srcDoc = DocumentHelper.parseText(srcXml);
            for (Iterator<String> it = xpathList.iterator(); it.hasNext();) {
                String xpath = it.next();
                removeNullIdElement(srcDoc, xpath);
            }
            resultXml = srcDoc.asXML();
        } catch (DocumentException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }

    /**
     * 更新xml文档中指定ID的元素,ID保持不变
     *
     * @param srcXml     原xml
     * @param newNodeXml 新xml节点
     * @param xmlNodeId  更新元素ID属性值
     * @return 更新元素后的xml文档
     */
    public static String updateElementById(String srcXml, String newNodeXml, String xmlNodeId) {
        String resultXml = null;
        try {
            Document docSrc = DocumentHelper.parseText(srcXml);
            Document newDocNode = DocumentHelper.parseText(newNodeXml);
            //获取要更新的目标节点
            Element updatedNode = docSrc.elementByID(xmlNodeId);
            //获取更新目标节点的父节点
            Element parentUpNode = updatedNode.getParent();
            //删除掉要更新的节点
            parentUpNode.remove(updatedNode);

            //获取新节点的根节点(作为写入节点)
            Element newRoot = newDocNode.getRootElement();
            //处理新节点的ID属性值和BS子元素的值
            if (newRoot.attribute("ID") == null) {
                newRoot.addAttribute("ID", xmlNodeId);
            } else {
                newRoot.attribute("ID").setValue(xmlNodeId);
            }
            //在原文档中更新位置写入新节点
            parentUpNode.add(newRoot);
            resultXml = docSrc.asXML();
        } catch (DocumentException e) {
            log.error("更新xml文档中ID为" + xmlNodeId + "节点发生异常,请检查!");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }

    /**
     * 更新xml文档中指定ID的元素,并检查ID和BS,加以设置
     *
     * @param srcXml     原xml
     * @param newNodeXml 新xml节点
     * @param xmlNodeId  更新元素ID属性值
     * @return 更新元素后的xml文档
     */
    public static String updateElementByIdAddIdBs(String srcXml, String newNodeXml, String xmlNodeId) {
        String resultXml = null;
        try {
            Document docSrc = DocumentHelper.parseText(srcXml);
            Document newDocNode = DocumentHelper.parseText(newNodeXml);
            //获取要更新的目标节点
            Element updatedNode = docSrc.elementByID(xmlNodeId);
            //获取更新目标节点的父节点
            Element parentUpNode = updatedNode.getParent();
            //删除掉要更新的节点
            parentUpNode.remove(updatedNode);

            //获取新节点的根节点(作为写入节点)
            Element newRoot = newDocNode.getRootElement();
            //处理新节点的ID属性值和BS子元素的值
            if (newRoot.attribute("ID") == null) {
                newRoot.addAttribute("ID", xmlNodeId);
            } else {
                newRoot.attribute("ID").setValue(xmlNodeId);
            }
            if (newRoot.element("BS") == null) {
                newRoot.addElement("BS", xmlNodeId);
            } else {
                newRoot.element("BS").setText(xmlNodeId);
            }
            //在原文档中更新位置写入新节点
            parentUpNode.add(newRoot);
            resultXml = docSrc.asXML();
        } catch (DocumentException e) {
            log.error("更新xml文档中ID为" + xmlNodeId + "节点发生异常,请检查!");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }

    /**
     * 为xml元素设置ID属性
     *
     * @param xmlElement 原xml元素
     * @return 设置id后的xml串
     */
    public static String addIdAttribute(String xmlElement) {
        String resultXml = null;
        try {
            Document srcDoc = DocumentHelper.parseText(xmlElement);
            Element root = srcDoc.getRootElement();
//            Long nextValue = SequenceUtils.getSequeceNextValue();
            Long nextValue = new Random().nextLong();
            root.addAttribute("ID", nextValue.toString());
            resultXml = root.asXML();
        } catch (DocumentException e) {
            log.error("给xml元素设置ID属性发生异常,请检查!");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }

    /**
     * 为xml元素设置ID属性,并将此属性写入一个指定子节点文本值域
     *
     * @param xmlElement 原xml元素
     * @param nodeName   (直接)子节点的名称,或相对当前节点的xpath路径
     * @return 设置id和子节点后的xml串
     */
    public static String addIdAndWriteNode(String xmlElement, String nodeName) {
        String resultXml = null;
        try {
            Document srcDoc = DocumentHelper.parseText(xmlElement);
            Element root = srcDoc.getRootElement();
//            Long nextValue = SequenceUtils.getSequeceNextValue();
            Long nextValue = new Random().nextLong();
            root.addAttribute("ID", nextValue.toString());
            Node bsElement = root.selectSingleNode(nodeName);
            if (bsElement instanceof Element && bsElement != null) {
                bsElement.setText(nextValue.toString());
            } else {
                root.addElement(nodeName).setText(nextValue.toString());
            }
            resultXml = root.asXML();
        } catch (DocumentException e) {
            log.error("给xml元素设置ID属性和直接" + nodeName + "子元素值时发生异常,请检查!");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return resultXml;
    }


    public static void main(String args[]) {
        test();
    }

    public static void test() {
        System.out.println("----------test()----------");
        String srcXml = "<?xml version=\"1.0\" encoding=\"GBK\"?>\n" +
                "<doc>\n" +
                "    <person>\n" +
                "        <name>某人</name>\n" +
                "        <adds>\n" +
                "            <add ID=\"10001\">\n" +
                "                <BS>10001</BS>\n" +
                "                <note>郑州市经三路</note>\n" +
                "            </add>\n" +
                "            <add ID=\"10002\">\n" +
                "                <BS>10002</BS>\n" +
                "                <note>西安市太白路</note>\n" +
                "            </add>\n" +
                "            <add ID=\"\">\n" +
                "                <BS>10002</BS>\n" +
                "                <note>空ID节点啊</note>\n" +
                "            </add>\n" +
                "            <add>\n" +
                "                <BS>10002</BS>\n" +
                "                <note>空ID节点啊</note>\n" +
                "            </add>\n" +
                "        </adds>\n" +
                "    </person>\n" +
                "    <other>\n" +
                "        <name ID=\"HEHE\">ASDF</name>\n" +
                "        <name>空ID节点啊</name>\n" +
                "        <name>空ID节点啊</name>\n" +
                "    </other>\n" +
                "</doc>";
        String nodeXml1 = "            <add >\n" +
                "                <BS></BS>\n" +
                "                <note>西安市太白路1</note>\n" +
                "            </add>";

        String nodeXml2 = "            <add>\n" +
                "                <note>西安市太白路2</note>\n" +
                "            </add>";

//        System.out.println("输出三个测试对象");
//        System.out.println("----------srcXml------------");
//        System.out.println(srcXml);
//        System.out.println("----------nodeXml1------------");
//        System.out.println(srcXml);
//        System.out.println("----------nodeXml2------------");
//        System.out.println(srcXml);

        System.out.println("----------addElement()测试----------");
        String addrs = addElement(srcXml, nodeXml1, "/doc/person/adds");
        System.out.println(addrs);

        System.out.println("----------addIdAttribute()测试----------");
        String addIdrs = addIdAttribute(nodeXml1);
        System.out.println(addIdrs);

        System.out.println("----------addIdAndWriteNode()测试----------");
        String addIdNoders = addIdAndWriteNode(nodeXml1, "BS");
        System.out.println(addIdNoders);

        System.out.println("----------removeElementById()测试----------");
        String removeIdrs = removeElementById(srcXml, "10001");
        System.out.println(removeIdrs);

        System.out.println("----------updateElementByIdAddIdBs()测试----------");
        String upbyidrs = updateElementByIdAddIdBs(srcXml, nodeXml2, "10001");
        System.out.println(upbyidrs);

        System.out.println("----------updateElementById()测试----------");
        String upbyidrs1 = updateElementById(srcXml, nodeXml2, "10001");
        System.out.println(upbyidrs1);

        System.out.println("----------removeNullIdElement()测试----------");
        String rvnullrs = removeNullIdElement(srcXml, "/doc/person/adds");
        System.out.println(rvnullrs);

        System.out.println("----------removeAllNullIdElement()测试----------");
        ArrayList<String> xpathList = new ArrayList<String>();
        xpathList.add("/doc/person/adds");
        xpathList.add("/doc/other");
        String rvallnullrs = removeAllNullIdElement(srcXml, xpathList);
        System.out.println(rvallnullrs);

        System.out.println("----------Dom4j生成一个xml测试----------");
        Document doc = DocumentHelper.createDocument();
        doc.addElement("root")
                .addElement("person").setText("haha:)");
        System.out.println(doc.asXML());
    }
}

运行结果:

----------test()----------
----------addElement()测试----------
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            <add ID="10001">
                <BS>10001</BS>
                <note>郑州市经三路</note>
            </add>
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            <add ID="">
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
            <add>
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
        <add>
                <BS/>
                <note>西安市太白路1</note>
            </add></adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>
----------addIdAttribute()测试----------
<add ID="7549173897283584063">
                <BS/>
                <note>西安市太白路1</note>
            </add>
----------addIdAndWriteNode()测试----------
<add ID="8974292836389323633">
                <BS>8974292836389323633</BS>
                <note>西安市太白路1</note>
            </add>
----------removeElementById()测试----------
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            <add ID="">
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
            <add>
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
        </adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>
----------updateElementByIdAddIdBs()测试----------
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            <add ID="">
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
            <add>
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
        <add ID="10001">
                <note>西安市太白路2</note>
            <BS xmlns="10001"/></add></adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>
----------updateElementById()测试----------
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            <add ID="">
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
            <add>
                <BS>10002</BS>
                <note>空ID节点啊</note>
            </add>
        <add ID="10001">
                <note>西安市太白路2</note>
            </add></adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>
----------removeNullIdElement()测试----------
16:17:32,689  INFO XmlNodeHandler:113 - 在/doc/person/adds下成功删除4了个空节点!
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            <add ID="10001">
                <BS>10001</BS>
                <note>郑州市经三路</note>
            </add>
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            
            
        </adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        <name>空ID节点啊</name>
        <name>空ID节点啊</name>
    </other>
</doc>
----------removeAllNullIdElement()测试----------
16:17:32,705  INFO XmlNodeHandler:113 - 在/doc/person/adds下成功删除4了个空节点!
16:17:32,705  INFO XmlNodeHandler:113 - 在/doc/other下成功删除3了个空节点!
<?xml version="1.0" encoding="GBK"?>
<doc>
    <person>
        <name>某人</name>
        <adds>
            <add ID="10001">
                <BS>10001</BS>
                <note>郑州市经三路</note>
            </add>
            <add ID="10002">
                <BS>10002</BS>
                <note>西安市太白路</note>
            </add>
            
            
        </adds>
    </person>
    <other>
        <name ID="HEHE">ASDF</name>
        
        
    </other>
</doc>
----------Dom4j生成一个xml测试----------
<?xml version="1.0" encoding="UTF-8"?>
<root><person>haha:)</person></root>

Process finished with exit code 0


程序中尽量避免用Dom4j之外的第三方类库,但是还用到了,可以修改一下:
1、用到Log4j的地方,可以修改log对象的实现为Java中的Logger工具:
      private static Logger log = Logger.getLogger(XmlNodeHandler.class.getName());

2、用到apache的beanutils工具包中的StringUtils.isBlank()方法时候,可以改用JDK API String中的equel()方法做比较进行替换。具体操作很简单,我就不写了。

3、用到Sequence生成的Id的地方,我都改为java.util.Random类来实现,目的还是为了把眼光放到Dom的处理上。如果有博友对这个SequenceUtils工具类有兴趣,我可以把代码贴出去。

本处理器工具类涵盖Dom4j最常用部分80%以上的API。放到以方便查看。

分享到:
评论

相关推荐

    DOM4j属性的详细介绍及相关的例子

    介绍DOM4J的主要接口的使用方法,Elementorg.dom4j....org.dom4j.NodeFilter:NodeFilter定义了在dom4j节点中产生的一个滤镜或谓词的行为 org.dom4j.ProcessingInstruction : ProcessingInstruction定义XML处理指令

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part4

    3.5.1 下载并配置dom4j 88 3.5.2 dom4j api介绍 88 3.5.3 第一个实例 92 3.5.4 第二个实例 94 3.6 解析名称空间 96 3.6.1 dom和名称空间 96 3.6.2 sax和名称空间 97 3.6.3 jdom和名称空间 98 3.6.4 dom4j和...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part2

    3.5.1 下载并配置dom4j 88 3.5.2 dom4j api介绍 88 3.5.3 第一个实例 92 3.5.4 第二个实例 94 3.6 解析名称空间 96 3.6.1 dom和名称空间 96 3.6.2 sax和名称空间 97 3.6.3 jdom和名称空间 98 3.6.4 dom4j和...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part5

    3.5.1 下载并配置dom4j 88 3.5.2 dom4j api介绍 88 3.5.3 第一个实例 92 3.5.4 第二个实例 94 3.6 解析名称空间 96 3.6.1 dom和名称空间 96 3.6.2 sax和名称空间 97 3.6.3 jdom和名称空间 98 3.6.4 dom4j和...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part3

    3.5.1 下载并配置dom4j 88 3.5.2 dom4j api介绍 88 3.5.3 第一个实例 92 3.5.4 第二个实例 94 3.6 解析名称空间 96 3.6.1 dom和名称空间 96 3.6.2 sax和名称空间 97 3.6.3 jdom和名称空间 98 3.6.4 dom4j和...

    xml入门教程/xml入门教程

    4) XML没有定义任何标记,它提供了一种工具定义标记以及它们之间的结构关系; 5) XML是一种用于结构化文本交换的标记语言; 6) XML代表了内容的结构也代表了内容本身; 7) XML继承自SGML(标准通用标记语言)。SGML的...

    jquery插件使用方法大全

    (4)的方法会在指定的Dom对象上绑定响应ajax执行的事件。 (5)同步加载数据。发送请求时锁住浏览器。需要锁定用户交互操作时使用同步方式。 var html = $.ajax({ url: "some.php", async: false }).responseText;...

    jQuery详细教程

    选择器允许您对 DOM 元素组或单个 DOM 节点进行操作。 jQuery 元素选择器 jQuery 使用 CSS 选择器来选取 HTML 元素。 $("p") 选取 &lt;p&gt; 元素。 $("p.intro") 选取所有 class="intro" 的 &lt;p&gt; 元素。 $("p#demo") 选取 ...

Global site tag (gtag.js) - Google Analytics