001 /*
002 * Copyright (c) 2007 Mozilla Foundation
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a
005 * copy of this software and associated documentation files (the "Software"),
006 * to deal in the Software without restriction, including without limitation
007 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
008 * and/or sell copies of the Software, and to permit persons to whom the
009 * Software is furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
017 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
019 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
020 * DEALINGS IN THE SOFTWARE.
021 */
022
023 package nu.validator.messages;
024
025 import java.util.SortedSet;
026
027 import org.xml.sax.SAXException;
028
029 import nu.validator.source.SourceHandler;
030 import nu.validator.xml.AttributesImpl;
031 import nu.validator.xml.XhtmlSaxEmitter;
032
033 public class XhtmlSourceHandler implements SourceHandler {
034
035 private static final char[] NEWLINE_SUBSTITUTE = {'\u21A9'};
036
037 private static final AttributesImpl LINE_BREAK_ATTRS = new AttributesImpl();
038
039 static {
040 LINE_BREAK_ATTRS.addAttribute("class", "lf");
041 LINE_BREAK_ATTRS.addAttribute("title", "Line break");
042 }
043
044 private final AttributesImpl attrs = new AttributesImpl();
045
046 private final XhtmlSaxEmitter emitter;
047
048 private boolean listOpen;
049
050 private boolean lineOpen;
051
052 private String rangeOpen;
053
054 private boolean charOpen;
055
056 private int lineNumber;
057
058 private boolean lineHadCharacters;
059
060 private SortedSet<Integer> oneBasedLineErrors;
061
062 /**
063 * @param emitter
064 */
065 public XhtmlSourceHandler(final XhtmlSaxEmitter emitter) {
066 this.emitter = emitter;
067 }
068
069 public void characters(char[] ch, int start, int length)
070 throws SAXException {
071 maybeOpen();
072 lineHadCharacters = true;
073 emitter.characters(ch, start, length);
074 }
075
076 @SuppressWarnings("boxing")
077 private void maybeOpen() throws SAXException {
078 assert !(lineOpen && !listOpen);
079 if (!listOpen) {
080 emitter.startElementWithClass("ol", "source");
081 listOpen = true;
082 }
083 if (!lineOpen) {
084 lineNumber++;
085 attrs.clear();
086 attrs.addAttribute("id", "l" + lineNumber);
087 if (oneBasedLineErrors != null && oneBasedLineErrors.contains(lineNumber)) {
088 attrs.addAttribute("class", "b");
089 }
090 emitter.startElement("li", attrs);
091 emitter.startElement("code");
092 lineOpen = true;
093 lineHadCharacters = false;
094 if (rangeOpen != null) {
095 emitter.startElementWithClass("b", rangeOpen);
096 }
097 }
098 }
099
100 public void endCharHilite() throws SAXException {
101 if (!charOpen) {
102 return;
103 }
104 emitter.endElement("b");
105 charOpen = false;
106 }
107
108 public void endRange() throws SAXException {
109 assert rangeOpen != null;
110 emitter.endElement("b");
111 rangeOpen = null;
112 }
113
114 public void endSource() throws SAXException {
115 if (charOpen) {
116 endCharHilite();
117 }
118 assert rangeOpen == null;
119 if (lineOpen) {
120 emitter.endElement("code");
121 emitter.endElement("li");
122 }
123 if (listOpen) {
124 emitter.endElement("ol");
125 }
126 }
127
128 public void newLine() throws SAXException {
129 maybeOpen();
130 if (charOpen) {
131 endCharHilite();
132 }
133 if (rangeOpen != null) {
134 emitter.endElement("b");
135 }
136 emitter.endElement("code");
137 emitter.startElement("code", LINE_BREAK_ATTRS);
138 emitter.characters(NEWLINE_SUBSTITUTE);
139 emitter.endElement("code");
140 emitter.endElement("li");
141 lineOpen = false;
142 }
143
144 public void startCharHilite(int oneBasedLine, int oneBasedColumn)
145 throws SAXException {
146 maybeOpen();
147 assert !charOpen;
148 assert lineNumber == oneBasedLine;
149 attrs.clear();
150 attrs.addAttribute("id", "cl" + oneBasedLine + "c" + oneBasedColumn);
151 emitter.startElement("b", attrs);
152 charOpen = true;
153 }
154
155 public void startRange(int oneBasedLine, int oneBasedColumn)
156 throws SAXException {
157 maybeOpen();
158 assert rangeOpen == null;
159 rangeOpen = "l" + oneBasedLine + "c" + oneBasedColumn;
160 attrs.clear();
161 attrs.addAttribute("id", rangeOpen);
162 attrs.addAttribute("class", rangeOpen);
163 emitter.startElement("b", attrs);
164 }
165
166 public void startSource(String type, String encoding) throws SAXException {
167 listOpen = false;
168 lineOpen = false;
169 rangeOpen = null;
170 charOpen = false;
171 lineNumber = 0;
172 oneBasedLineErrors = null;
173 }
174
175 public void setLineErrors(SortedSet<Integer> oneBasedLineErrors) throws SAXException {
176 this.oneBasedLineErrors = oneBasedLineErrors;
177 }
178
179 }