JavaRush /בלוג Java /Random-HE /יסודות XML עבור מתכנת Java - חלק 3.1 מתוך 3 - SAX
Ярослав
רָמָה
Днепр

יסודות XML עבור מתכנת Java - חלק 3.1 מתוך 3 - SAX

פורסם בקבוצה
הקדמה שלום לכל הקוראים של המאמר שעדיין לא האחרון שלי, ואני רוצה לברך אתכם: הדברים המסובכים בנושא XML מאחורינו. מאמר זה יכיל קוד ב-Java. תהיה קצת תיאוריה, ואז תרגול. בשל העובדה שחומר אחד ב-SAX מילא 10 עמודים בוורד, הבנתי שאני לא יכול להיכנס לגבולות. לכן, מאמר 3 יחולק ל-3 מאמרים נפרדים, כמה שזה נשמע מוזר. הכל יהיה בסדר הזה: SAX -> DOM -> JAXB. מאמר זה יתמקד רק ב-SAX. נ.ב. הייתה משימה איפשהו בקורס בה היה צורך להציג את כל האלמנטים הפנימיים בקובץ HTML. לאחר מאמר זה, תוכלו לעשות זאת מבלי לקרוא שורה אחר שורה BufferedReaderבאלגוריתמי עיבוד קונבנציונליים ומורכבים, וגם פתרון דומה יינתן בדוגמה המעשית האחרונה. בואו נתחיל :) SAX (API פשוט ל-XML) - תיאוריה המטפל SAX מעוצב בצורה כזו שהוא פשוט קורא קבצי XML ברצף ומגיב לאירועים שונים, ולאחר מכן הוא מעביר את המידע למטפל מיוחד באירועים. יש לו לא מעט אירועים, אבל התכופים והשימושיים ביותר הם הבאים:
  1. startDocument- תחילת המסמך
  2. endDocument- סוף המסמך
  3. startElement- פתיחת אלמנט
  4. endElement- סגירת אלמנט
  5. characters- מידע טקסט בתוך אלמנטים.
כל האירועים מעובדים במטפל באירועים , שיש ליצור אותו ולעקוף את השיטות . יתרונות: ביצועים גבוהים בשל השיטה ה"ישירה" לקריאת נתונים, עלויות זיכרון נמוכות. חסרונות: פונקציונליות מוגבלת, כלומר בבעיות לא ליניאריות נצטרך לשכלל אותה. SAX (API פשוט ל-XML) - תרגל מיד רשימה של ייבוא ​​כדי שלא תחפש ותבלבל שום דבר:
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
כעת, ראשית, עלינו ליצור SAXParser:
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Creation фабрики и образца parserа
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }
}
כפי שאתה יכול לראות, תחילה עליך ליצור מפעל, ולאחר מכן ליצור את המנתח עצמו במפעל. עכשיו, כשיש לנו את המנתח עצמו, אנחנו צריכים מטפל לאירועים שלו. בשביל זה אנחנו צריכים כיתה נפרדת לנוחיותנו:
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startDocument() throws SAXException {
            // Тут будет логика реакции на начало documentа
        }

        @Override
        public void endDocument() throws SAXException {
            // Тут будет логика реакции на конец documentа
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            // Тут будет логика реакции на начало element
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            // Тут будет логика реакции на конец element
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на текст между elementми
        }

        @Override
        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на пустое пространство внутри элементов (пробелы, переносы строчек и так далее).
        }
    }
}
יצרנו מחלקה עם כל השיטות שהיינו צריכים כדי לטפל באירועים שהיו רשומים בתיאוריה. עוד קצת תיאוריה נוספת: קצת על characters: אם האלמנט מכיל טקסט, למשל, " שלום ", אז, תיאורטית, השיטה יכולה להיקרא 5 פעמים ברציפות עבור כל דמות בודדת, אבל זה לא עניין גדול, כי הכל עדיין יעבוד. על שיטות startElementו endElement:uri - זה החלל שבו נמצא האלמנט, localName- זה השם של האלמנט ללא קידומת, qName- זה השם של האלמנט עם קידומת (אם יש כזו, אחרת רק השם של האלמנט). uriותמיד localNameריק אם לא אפשרנו עיבוד שטח במפעל. זה נעשה בשיטת המפעל setNamespaceAware(true). אז נוכל לקבל רווח ( uri) ואלמנטים עם קידומות לפניהם ( localName). משימה מס' 1 - יש לנו את ה-XML הבא
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee name="Maksim" job="Middle Software Developer" />
                <employee name="Ivan" job="Junior Software Developer" />
                <employee name="Franklin" job="Junior Software Developer" />
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee name="Herald" job="Middle Software Developer" />
                <employee name="Adam" job="Middle Software Developer" />
                <employee name="Leroy" job="Junior Software Developer" />
            </employees>
        </office>
    </offices>
</company>
המטרה שלנו: לקבל את כל המידע על כל העובדים מהקובץ הזה. ראשית, עלינו ליצור מחלקהEmployee:
public class Employee {
    private String name, job;

    public Employee(String name, String job) {
        this.name = name;
        this.job = job;
    }

    public String getName() {
        return name;
    }

    public String getJob() {
        return job;
    }
}
ובכיתה הראשית שלנו SAXExampleאנחנו צריכים רשימה עם כל העובדים:
private static ArrayList<Employee> employees = new ArrayList<>();
כעת נבחן היטב היכן נמצא המידע שאנו צריכים בקובץ ה-XML. וכפי שאנו יכולים לראות, כל המידע שאנו צריכים הוא התכונות של האלמנטים employee. ומכיוון startElementשיש לנו פרמטר שימושי כמו attributes, אז יש לנו משימה פשוטה למדי. ראשית, בואו נסיר שיטות מיותרות כדי לא לבלבל את הקוד שלנו. אנחנו צריכים רק את startElement. ובשיטה עצמה, עלינו לאסוף מידע מהתכונות של תג העובד. תשומת הלב:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
ההיגיון פשוט: אם השם של אלמנט הוא employee, פשוט נקבל מידע על התכונות שלו. יש attributesשיטה שימושית שבה, לדעת את השם של תכונה, אתה יכול לקבל את הערך שלה. זה מה שהשתמשנו. כעת, לאחר שיצרנו מטפל באירועים לתחילתו של אלמנט, עלינו לנתח את קובץ ה-XML שלנו . כדי לעשות זאת, פשוט עשה זאת:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        XMLHandler handler = new XMLHandler();
        parser.parse(new File("resource/xml_file1.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Name сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
בשיטת הניתוח עליך להעביר את הנתיב לקובץ ה-xml ולמטפל שיצרת. וכך, באמצעות הקוד הזה חילצנו מידע מה-XML הזה:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee name="Maksim" job="Middle Software Developer" />
                <employee name="Ivan" job="Junior Software Developer" />
                <employee name="Franklin" job="Junior Software Developer" />
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee name="Herald" job="Middle Software Developer" />
                <employee name="Adam" job="Middle Software Developer" />
                <employee name="Leroy" job="Junior Software Developer" />
            </employees>
        </office>
    </offices>
</company>
וקיבלנו את הפלט הבא:
Name сотрудника: Maksim, его должность: Middle Software Developer
Name сотрудника: Ivan, его должность: Junior Software Developer
Name сотрудника: Franklin, его должность: Junior Software Developer
Name сотрудника: Herald, его должность: Middle Software Developer
Name сотрудника: Adam, его должность: Middle Software Developer
Name сотрудника: Leroy, его должность: Junior Software Developer
המשימה הושלמה! משימה מס' 2 - יש לנו את ה-XML הבא:
<?xml version="1.0" encoding="UTF-8"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee>
                    <name>Maksim</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Ivan</name>
                    <job>Junior Software Developer</job>
                </employee>
                <employee>
                    <name>Franklin</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee>
                    <name>Herald</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Adam</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Leroy</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
    </offices>
</company>
המטרה שלנו: לקבל את כל המידע על כל העובדים מהקובץ הזה. בעיה זו תדגים היטב כיצד קובץ XML בעל מבנה גרוע יכול להקשות על כתיבת קוד. כפי שאתה יכול לראות, מידע על שם ומיקום מאוחסן כעת כמידע טקסט בתוך האלמנטים nameו job. כדי לקרוא טקסט בתוך אלמנטים, יש לנו את שיטת התווים. לשם כך, עלינו ליצור מחלקת מטפל חדשה עם לוגיקה משופרת. אל תשכח שמטפלים הם מחלקות מן המניין המסוגלות לאחסן לוגיקה בכל מורכבות. לכן, כעת נכוון את המעבד שלנו. למעשה, מספיק לציין שאנחנו תמיד nameמתחלפים job, וזה לא משנה באיזה סדר, אנחנו יכולים בקלות לשמור את השם והמקצוע למשתנים נפרדים, וכששני המשתנים נשמרים, ליצור את העובד שלנו. רק שכאן, יחד עם תחילת האלמנט, אין לנו פרמטר לטקסט בתוך האלמנט. אנחנו צריכים להשתמש בשיטות על טקסט. אבל איך אנחנו מקבלים מידע טקסט בתוך אלמנט אם אלו שיטות שונות לחלוטין? הפתרון שלי: אנחנו רק צריכים לזכור את שם האלמנט האחרון, ולבדוק charactersבאיזה אלמנט אנחנו קוראים את המידע. אתה גם צריך לזכור ש<codee>תווים קוראים את כל התווים בתוך אלמנטים, מה שאומר שכל הרווחים ואפילו מעברי השורות ייקראו. ואנחנו לא צריכים אותם. עלינו להתעלם מהנתונים האלה כי הם שגויים.</codee> קוד:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        AdvancedXMLHandler handler = new AdvancedXMLHandler();
        parser.parse(new File("resource/xml_file2.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Name сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class AdvancedXMLHandler extends DefaultHandler {
        private String name, job, lastElementName;

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            lastElementName = qName;
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String information = new String(ch, start, length);

            information = information.replace("\n", "").trim();

            if (!information.isEmpty()) {
                if (lastElementName.equals("name"))
                    name = information;
                if (lastElementName.equals("job"))
                    job = information;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if ( (name != null && !name.isEmpty()) && (job != null && !job.isEmpty()) ) {
                employees.add(new Employee(name, job));
                name = null;
                job = null;
            }
        }
    }
}
כפי שאתה יכול לראות, בגלל הסיבוך הבנאלי של מבנה קובץ ה-XML, הקוד שלנו הפך מסובך משמעותית. עם זאת, הקוד אינו מסובך. תיאור: יצרנו משתנים לאחסון נתונים על העובד ( name, job) , וכן משתנה lastElementNameלתיעוד באיזה אלמנט אנחנו נמצאים. אחרי זה, בשיטה charactersאנחנו מסננים את המידע, ואם עדיין יש שם מידע, אז זה אומר שזה הטקסט שאנחנו צריכים, ואז אנחנו קובעים אם זה שם או מקצוע באמצעות lastElementName. בשיטה endElementאנו בודקים אם כל המידע נקרא, ואם כן, אנו יוצרים עובד ומאפסים את המידע. הפלט של הפתרון שווה לדוגמא הראשונה:
Name сотрудника: Maksim, его должность: Middle Software Developer
Name сотрудника: Ivan, его должность: Junior Software Developer
Name сотрудника: Franklin, его должность: Junior Software Developer
Name сотрудника: Herald, его должность: Middle Software Developer
Name сотрудника: Adam, его должность: Middle Software Developer
Name сотрудника: Leroy, его должность: Junior Software Developer
לפיכך, בעיה זו נפתרה , אך אתה יכול לשים לב שהמורכבות גבוהה יותר. לכן, אנו יכולים להסיק שאחסון מידע טקסט בתכונות לרוב יהיה נכון יותר מאשר באלמנטים בודדים. ועוד משימה מתוקה שתפתור חלקית את הבעיה ב-JavaRush לגבי הצגת מידע על אלמנט ב-HTML, רק שיהיה צורך לערוך אותו מעט, כאן פשוט נפרט את כל האלמנטים בתוך אלמנט :) משימה מס' 3 - בהינתן אלמנט האלמנט, הצג את השמות והתכונות של כל האלמנטים הפנימיים; אם האלמנט לא נמצא, הצג זאת. למשימה זו נשתמש בקובץ ה-XML הבא:
<?xml version="1.0" encoding="UTF-8"?>
<root>
    <oracle>
        <connection value="jdbc:oracle:thin:@10.220.140.48:1521:test1" />
        <user value="secretOracleUsername" />
        <password value="111" />
    </oracle>

    <mysql>
        <connection value="jdbc:mysql:thin:@10.220.140.48:1521:test1" />
        <user value="secretMySQLUsername" />
        <password value="222" />
    </mysql>
</root>
כפי שאתה יכול לראות, יש לנו כאן שלושה תרחישים אפשריים: root, mysql, oracle. אז התוכנית תציג את כל המידע על כל האלמנטים בפנים. איך אנחנו יכולים לעשות את זה? וזה די פשוט: אנחנו רק צריכים להכריז על משתנה לוגי isEntered, שיציין אם אנחנו צריכים את האלמנט בפנים, ואם בפנים, לקרוא את כל הנתונים מ- startElement. קוד פתרון:
public class SAXExample {
    private static boolean isFound;

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        SearchingXMLHandler handler = new SearchingXMLHandler("root");
        parser.parse(new File("resource/xml_file3.xml"), handler);

        if (!isFound)
            System.out.println("Элемент не был найден.");
    }

    private static class SearchingXMLHandler extends DefaultHandler {
        private String element;
        private boolean isEntered;

        public SearchingXMLHandler(String element) {
            this.element = element;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (isEntered) {
                System.out.println(String.format("Найден элемент <%s>, его атрибуты:", qName));

                int length = attributes.getLength();
                for(int i = 0; i < length; i++)
                    System.out.println(String.format("Name атрибута: %s, его meaning: %s", attributes.getQName(i), attributes.getValue(i)));
            }

            if (qName.equals(element)) {
                isEntered = true;
                isFound = true;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.equals(element))
                isEntered = false;
        }
    }
}
בקוד הזה, כשמכניסים אלמנט שעליו אנחנו צריכים מידע, אנחנו מגדירים את הדגל isEnteredל-true, כלומר אנחנו בתוך האלמנט. וברגע שאנחנו בתוך האלמנט, אנחנו פשוט מעבדים כל אלמנט חדש startElement, מתוך ידיעה שזה בדיוק אלמנט פנימי של האלמנט שלנו. אז אנחנו פלט את שם האלמנט ואת הכותרת שלו. אם האלמנט לא נמצא בקובץ, אז יש לנו משתנה isFoundשנקבע כאשר האלמנט נמצא, ואם הוא false, תוצג הודעה שהאלמנט לא נמצא. וכפי שניתן לראות, בדוגמה SearchingXMLHandlerהעברנו rootאלמנט לבנאי. מסקנה עבורו:
Найден элемент <oracle>, его атрибуты:
Найден элемент <connection>, его атрибуты:
Name атрибута: value, его meaning: jdbc:oracle:thin:@10.220.140.48:1521:test1
Найден элемент <user>, его атрибуты:
Name атрибута: value, его meaning: secretOracleUsername
Найден элемент <password>, его атрибуты:
Name атрибута: value, его meaning: 111
Найден элемент <mysql>, его атрибуты:
Найден элемент <connection>, его атрибуты:
Name атрибута: value, его meaning: jdbc:mysql:thin:@10.220.140.48:1521:test1
Найден элемент <user>, его атрибуты:
Name атрибута: value, его meaning: secretMySQLUsername
Найден элемент <password>, его атрибуты:
Name атрибута: value, его meaning: 222
לפיכך, קיבלנו את כל המידע על האלמנטים הפנימיים ותכונותיהם. הבעיה נפתרה. <h2>אפילוג</h2>ראית ש-SAX הוא כלי די מעניין ודי יעיל, וניתן להשתמש בו בדרכים שונות, למטרות שונות וכן הלאה, אתה רק צריך להסתכל על הבעיה מימין צד, כפי שמוצג במשימה מס' 2 ומספר 3, שבה SAX לא סיפקה שיטות ישירות לפתרון הבעיה, אבל, הודות לכושר ההמצאה שלנו, הצלחנו למצוא דרך לצאת מהמצב. החלק הבא של המאמר יוקדש כולו ל-DOM. אני מקווה שנהניתם להכיר את SAX. ניסוי, תרגל ותבין שהכל די פשוט. וזה הכל, בהצלחה עם התכנות שלך ומצפה לחלק על ה-DOM בקרוב. בהצלחה בלימודים :) מאמר קודם: [תחרות] יסודות XML למתכנת Java - חלק 2 מתוך 3 המאמר הבא: [תחרות] יסודות XML למתכנת Java - חלק 3.2 מתוך 3 - DOM
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION