001    /**
002     * This package is DOMLET!!!
003     */
004    package net.sf.jolene.dom;
005    
006    import net.sf.jolene.constants.Prefs;
007    import net.sf.jolene.constants.Tags;
008    import net.sf.jolene.html.Attributes;
009    import net.sf.jolene.html.IAttributes;
010    import org.apache.log4j.LogManager;
011    import org.apache.log4j.Logger;
012    
013    import java.io.*;
014    import java.util.*;
015    
016    /**
017     * HTML Document object.
018     *
019     * @author Dan Howard
020     * @since Sep 21, 2003 5:22:33 PM
021     */
022    public final class Document {
023    
024        // todo modifiy file constructor to use File object - add constructor passing in the content of the document
025    
026        private static final Logger log = LogManager.getLogger(Document.class);
027    
028        // These are default protection so they are available to the Parser. The Parser's job is
029        // to initialize and define these objects.
030        String title;
031        List<Header> head;
032        String doctype;     // Store the text before the start of HTML - usually !DOCTYPE
033        String bodyText;    // Body text string.
034    
035        List<Form> forms;
036        IAttributes body;
037    
038        private String fileName;
039        private String contextPath;
040        private String uri;
041    
042        /**
043         * Indicator for labels being translated.  We do not do the translations here.
044         * We expect that the translation is done at the application level. This can
045         * therefore be used to decide if the document is already translated (so you
046         * don't need to do it over again.
047         * <p/>
048         * see net.sf.jolene.struts.DomletAction for the struts implementation
049         */
050        private boolean translated;
051    
052        /**
053         * @param fileName
054         * @param contextPath
055         * @param uri
056         * @throws IOException
057         * @throws MalformedHTMLException - runtime exception
058         */
059        public Document(String fileName, String contextPath, String uri) throws IOException, MalformedHTMLException {
060            this(fileName);
061            this.contextPath = contextPath;
062            this.uri = uri;
063            translatePaths();
064        }
065    
066        /**
067         * @param fileName - file name of the document.
068         * @throws IOException            - if an IOException occurs
069         * @throws MalformedHTMLException - runtime exception
070         */
071        public Document(String fileName) throws IOException, MalformedHTMLException {
072            this();
073            this.fileName = fileName;
074            open();
075        }
076    
077        /**
078         * For clone only.
079         */
080        Document() {
081            title = "";
082            head = new ArrayList<Header>(4);
083            doctype = "";
084            bodyText = "";
085            contextPath = "";
086            uri = "";
087            body = new Attributes();
088            forms = new ArrayList<Form>(4);
089            uri = "";
090        }
091    
092    
093        /**
094         * Adds a header element to the document.
095         *
096         * @param header a Header object.
097         * @return boolean to indicate success.
098         */
099        public boolean addHeader(Header header) {
100            return head.add(header);
101        }
102    
103        /**
104         * This is a custom clone implementation which ensures the document
105         * is cleanly and safely cloned. Used by the DocumentFactory.
106         *
107         * @return Document
108         */
109        @Override
110        public Document clone() {
111            Document doc = new Document();
112            doc.bodyText = bodyText;
113            doc.fileName = fileName;
114            doc.uri = uri;
115            doc.doctype = doctype;
116            doc.title = title;
117    
118            Iterator<Header> ith;
119            ith = head.iterator();
120            while (ith.hasNext()) {
121                doc.head.add(ith.next().clone());
122            }
123    
124            Iterator it;
125            doc.body = new Attributes();
126            it = body.keySet().iterator();
127            while (it.hasNext()) {
128                Object key;
129                Object value;
130                key = it.next();
131                value = body.getAttribute(key.toString());
132                doc.body.setAttribute(key.toString(), value.toString());
133            }
134    
135            doc.forms = new ArrayList<Form>(forms.size());
136            for (int i = 0; i < forms.size(); i++) {
137                doc.forms.add(forms.get(i).clone());
138            }
139    
140            return doc;
141        }
142    
143        /**
144         * Returns a object by searching all forms in the document.
145         *
146         * @param id The id of the element.
147         * @return HTMLElement
148         */
149        public HTMLElement elements(String id) {
150            HTMLElement element = null;
151    
152            if (forms != null && forms.size() > 0) {
153                for (int j = 0; j < forms.size(); j++) {
154                    element = forms.get(j).elements(id);
155                    if (element != null) {
156                        return element;
157                    }
158                }
159            }
160    
161            return element;
162        }
163    
164        /**
165         * Returns the form object specified by the number.
166         *
167         * @param index form number (starting with 0)
168         * @return Form a Form object
169         * @throws IndexOutOfBoundsException - if the index is out of range (index < 0 || index >= size()).
170         */
171        public Form forms(int index) {
172            return forms.get(index);
173        }
174    
175        /**
176         * Returns a form object specified by name. For the form to be found it will require a 'name' attribute.
177         * @param name name of the form in the document (case insensitive).
178         * @return Form a Form object or null if not found.
179         */
180        public Form forms(String name) {
181            for (Form form: forms) {
182                if (name.equalsIgnoreCase(form.getName())) {
183                    return form;
184                }
185            }
186            return null;
187        }
188    
189        /**
190         * Retrives the body attributes.
191         *
192         * @return IAttributes
193         */
194        public IAttributes getBody() {
195            return body;
196        }
197    
198        /**
199         * Gets the doc type of the document.
200         *
201         * @return String
202         */
203        public String getDoctype() {
204            return doctype;
205        }
206    
207    
208        /**
209         * @return Number of forms in the document.
210         */
211        public int getFormCount() {
212            return forms.size();
213        }
214    
215        /**
216         * Returns the number of Header elements in the document.
217         *
218         * @return int number of Header elements.
219         */
220        public int getHeaderCount() {
221            return head.size();
222        }
223    
224        /**
225         * Gets the document title.
226         *
227         * @return title
228         */
229        public String getTitle() {
230            return title;
231        }
232    
233        /**
234         * @return uri string for this document.
235         */
236        public String getUri() {
237            return uri;
238        }
239    
240        /**
241         * Return the Header element specified by the index based on document order.
242         *
243         * @param header to the Header (starting with 0).
244         * @return Header element.
245         */
246        public Header headers(int header) {
247            return head.get(header);
248        }
249    
250        /**
251         * Indicator for labels being translated.  We do not do the translations here.
252         * We expect that the translation is done at the application level. This can
253         * therefore be used to decide if the document is already translated (so you
254         * don't need to do it over again.
255         * <p/>
256         * see net.sf.jolene.struts.DomletAction for the struts implementation
257         *
258         * @return true if this document is translated
259         */
260        public boolean isTranslated() {
261            return translated;
262        }
263    
264        /**
265         * Remove the headers
266         * @param index to the header in the document. Zero based in document order.
267         * @return The removed header object.
268         */
269        public Header removeHeader(int index) {
270            return head.remove(index);
271        }
272    
273        /**
274         * Sets the doc type of the document.
275         *
276         * @param doctype The HTML doctype string.
277         */
278        public void setDoctype(String doctype) {
279            this.doctype = doctype;
280        }
281    
282        /**
283         * Set the document title.
284         *
285         * @param string The document title.
286         */
287        public void setTitle(String string) {
288            title = string;
289        }
290    
291        /**
292         * Indicator for labels being translated.  We do not do the translations here.
293         * We expect that the translation is done at the application level. This can
294         * therefore be used to decide if the document is already translated (so you
295         * don't need to do it over again.
296         * see net.sf.jolene.struts.DomletAction for the struts implementation
297         *
298         * @param translated set to true if the doc has been translated.
299         */
300        public void setTranslated(boolean translated) {
301            this.translated = translated;
302        }
303    
304        /**
305         * Writes the document to the specified fileName.
306         *
307         * @param filename The filename to write to.
308         * @throws java.io.IOException if there's an IOException
309         */
310        public void stream(String filename) throws IOException {
311            BufferedWriter out = null;
312            try {
313                out = new BufferedWriter(new FileWriter(filename));
314                stream(out);
315            } finally {
316                if (out != null) {
317                    try {
318                        out.close();
319                    } catch (IOException e) {
320                    }
321                }
322            }
323        }
324    
325        /**
326         * Writes the document to the specified Writer object and closes the Writer.
327         *
328         * @param out an output Writer to write to.
329         * @throws java.io.IOException it there's an IOException
330         */
331        public void stream(Writer out) throws IOException {
332            try {
333                String ls = System.getProperty("line.separator");
334    
335                if (doctype != null && doctype.trim().length() > 0) {
336                    out.write(doctype + ls);
337                }
338                out.write("<html>" + ls);
339                out.write("<head>" + ls);
340                for (Header aHead : head) {
341                    out.write(aHead.getBeforeText() + aHead + aHead.getAfterText() + ls);
342                }
343                out.write("<title>" + getTitle() + "</title>" + ls);
344                out.write("</head>" + ls);
345    
346                out.write("<body ");
347                for (String s : body.keySet()) {
348                    out.write(s + "=\"" + body.getAttribute(s) + "\" ");
349                }
350                out.write(">" + ls);
351    
352                // For each form, put the text from the start of the body text to
353                // the startpoint of the form.
354                int offset;
355                offset = 0;
356                Form form;
357    
358                for (int j = 0; j < forms.size(); j++) {
359                    form = (Form) forms(j);
360                    // Write whatever text exist between previous form (or start of doc) and current form.
361                    out.write(bodyText.substring(offset, form.getStartPoint()));
362                    out.write(form.getBeforeText() + form + form.getAfterText());
363                    offset = form.getEndPoint() + 1;
364    
365                    for (int k = 0; k < form.elements.size(); k++) {
366                        //System.out.print("k = " + k + " " + form.elements(k).startPoint + " " + form.elements(k).endPoint + " " + offset);
367                        //System.out.println(" ID: " + form.elements(k).getAttribute("id"));
368    
369                        out.write(bodyText.substring(offset, form.elements(k).getStartPoint()));
370                        out.write(form.elements(k).getBeforeText() + form.elements(k) + form.elements(k).getAfterText());
371                        offset = form.elements(k).getEndPoint() + 1;
372                    }
373    
374                    // if this is the last form write the remaining text.
375                    if (j == forms.size() - 1) {
376                        out.write(bodyText.substring(offset, bodyText.length()));
377                    }
378                }
379    
380                out.write(ls);
381                out.write("</body>" + ls);
382                out.write("</html>");
383                out.flush();
384            } finally {
385                try {
386                    out.close();
387                } catch (IOException e) {
388                }
389            }
390        }
391    
392        /**
393         * Returns the document file name.
394         *
395         * @return String
396         */
397        public String toString() {
398            return fileName;
399        }
400    
401        //================================================
402    
403        void open() throws IOException, MalformedHTMLException {
404            long starttime = System.currentTimeMillis();
405            long fileSize = new File(fileName).length();
406    
407            log.debug("FileName:" + fileName + " fileSize: " + fileSize);
408            BufferedReader reader = new BufferedReader(new FileReader(fileName));
409            char[] buff = new char[(int) fileSize];
410    
411            String fileBuffer = "";
412            try {
413                int n = reader.read(buff, 0, (int) fileSize);
414    
415                // Convert char array to StringBuffer
416                fileBuffer = new String(buff, 0, n);
417            }
418    
419            finally {
420                try {
421                    reader.close();
422                } catch (IOException e) {
423                }
424            }
425    
426            String workBuffer = new String(fileBuffer);
427    
428            new Parser(workBuffer, this);
429    
430            if ("true".equalsIgnoreCase(Prefs.GenerateLocaleStrings.getValue())) {
431                generateLocaleStrings();
432            }
433    
434            log.info("Time to open doc: " + (System.currentTimeMillis() - starttime));
435        }
436    
437        private void generateLocaleStrings() throws IOException {
438            Properties properties = new Properties();
439    //@todo - so many issues here. Null pointer possibility, What value for objects? Which objects? Should they include html? NO! Wrong place for the code. Where to create the file?
440    
441            FileOutputStream out = null;
442            FileInputStream in = null;
443            try {
444                File f = new File("resources/application.properties");
445                f.createNewFile();
446                in = new FileInputStream(f);
447                properties.load(in);
448                out = new FileOutputStream(f);
449    
450                Form ff;
451                StringBuilder objectName;
452    
453                log.info("FILENAME: " + this.fileName);
454                String docName;
455                docName = this.fileName.substring(this.fileName.lastIndexOf("/") + 1);
456                docName = docName.substring(0, docName.indexOf("."));
457                log.info("DOC NAME: " + docName);
458                for (int j = 0; j < forms.size(); j++) {
459                    objectName = new StringBuilder();
460                    objectName.append(docName).append('.');
461    
462                    ff = (Form) forms(j);
463                    if (ff.getName().trim().length() > 0) {
464                        objectName.append(ff.getName()).append('.');
465                    } else {
466                        objectName.append("form").append(j + 1).append('.');
467                    }
468    
469                    String elementName;
470                    HTMLElement element;
471                    for (int k = 0; k < ff.elements.size(); k++) {
472                        element = ff.elements(k);
473                        if (element.hasAttribute("name")) {
474                            elementName = element.getAttribute("name");
475                        } else if (element.hasAttribute("id")) {
476                            elementName = element.getAttribute("id");
477                        } else {
478                            elementName = element.getTag().toString() + k;
479                        }
480                        if (element.getValue() != null) {
481                            properties.put(objectName + elementName, element.getValue());
482                        }
483                    }
484                }
485                properties.store(out, "---Locale strings written by jolene---");
486                out.close();
487                in.close();
488            } finally {
489                if (out != null) {
490                    try {
491                        out.close();
492                    } catch (IOException e) {
493                        log.warn(e.getMessage(), e);
494                    }
495                    try {
496                        in.close();
497                    } catch (IOException e) {
498                        log.warn(e.getMessage(), e);
499                    }
500                }
501            }
502        }
503    
504        //--------------------------------------------------------
505    
506        //@TODO Add call for JUnit tests
507    
508        void streamObjects() {
509            Form ff;
510    
511            for (int j = 0; j < forms.size(); j++) {
512                ff = (Form) forms(j);
513    
514                System.out.println("FORM: " + j + ff);
515                for (int k = 0; k < ff.elements.size(); k++) {
516                    System.out.print("" + ff.elements(k));
517                    if (ff.elements(k).hasAttribute("name")) {
518                        System.out.println("NAME: " + ff.elements(k).getAttribute("name"));
519                    } else {
520                        if (ff.elements(k).hasAttribute("id")) {
521                            System.out.println("ID: " + ff.elements(k).getAttribute("ID"));
522                        } else {
523                            System.out.println();
524                        }
525                    }
526                }
527            }
528        }
529    
530        /**
531         * For JUnit
532         */
533        void streamResults(Writer writer) throws IOException {
534            for (int j = 0; j < forms.size(); j++) {
535                writer.write("form : " + j + " " + forms(j) + "\n");
536                for (int k = 0; k < forms(j).elements.size(); k++) {
537                    writer.write("element : " + k + "\n" + forms(j).elements(k) + "\n");
538                }
539            }
540        }
541    
542        private void translatePaths() {
543            HTMLElement element;
544            String src;
545    
546            for (int j = 0; j < this.forms.size(); j++) {
547                for (int k = 0; k < this.forms.get(j).getElementCount(); k++) {
548                    element = this.forms.get(j).elements(k);
549                    if (element.getTag().equals(Tags.img) && element.hasAttribute("src")) {
550                        src = translatePath(element.getAttribute("src"));
551                        element.setAttribute("src", src);
552                    }
553                }
554            }
555    
556            for (int j = 0; j < this.head.size(); j++) {
557                element = this.head.get(j);
558    
559                if (element.tag == Tags.script && element.hasAttribute("src")) {
560                    src = translatePath(element.getAttribute("src"));
561                    element.setAttribute("src", src);
562                } else if (element.tag == Tags.link) {
563                    if (element.hasAttribute("href")) {
564                        src = translatePath(element.getAttribute("href"));
565                        element.setAttribute("href", src);
566                    }
567    
568                    if (element.hasAttribute("src")) {
569                        src = translatePath(element.getAttribute("srg"));
570                        element.setAttribute("src", src);
571                    }
572                }
573            }
574        }
575    
576        private String translatePath(String path) {
577            if (path == null) {
578                // If an element does not have a src or href attribute then it will be assigned blank
579                return "";
580            }
581            if (path.toLowerCase().startsWith("http")) {
582                // leave it alone, the path exists on some other server.
583                return path;
584            }
585            path = path.replace('\\', '/');
586            StringTokenizer st = new StringTokenizer(path, "/");
587            StringBuffer endPath = new StringBuffer("");
588            String pathElement;
589            while (st.hasMoreElements()) {
590                pathElement = st.nextElement().toString();
591                if (pathElement.equals(".") || pathElement.equals("..")) {
592                } else {
593                    endPath.append('/').append(pathElement);
594                }
595            }
596    
597            path = contextPath + endPath;
598    
599            return path;
600        }
601    }