String (@NotNull CharSequence content) { final Ref<String> charsetNameRef = new Ref<>(); try { new HtmlBuilderDriver(content).build(new XmlBuilder() { final @NonNls Set<String> inTag = new HashSet<>(); boolean metHttpEquiv; boolean metHtml5Charset; @Override public void doctype(final @Nullable CharSequence publicId, final @Nullable CharSequence systemId, final int startOffset, final int endOffset) { } @Override public ProcessingOrder startTag(final CharSequence localName, final String namespace, final int startOffset, final int endOffset, final int headerEndOffset) { @NonNls String name = StringUtil.toLowerCase(localName.toString()); inTag.add(name); if (!inTag.contains("head") && !"html".equals(name)) terminate(); return ProcessingOrder.TAGS_AND_ATTRIBUTES; } private static void terminate() { throw TerminateException.INSTANCE; } @Override public void endTag(final CharSequence localName, final String namespace, final int startoffset, final int endoffset) { final @NonNls String name = StringUtil.toLowerCase(localName.toString()); if ("meta".equals(name) && (metHttpEquiv || metHtml5Charset) && contentAttributeValue != null) { String charsetName; if (metHttpEquiv) { int start = contentAttributeValue.indexOf(CHARSET_PREFIX); if (start == -1) return; start += CHARSET_PREFIX.length(); int end = contentAttributeValue.indexOf(';', start); if (end == -1) end = contentAttributeValue.length(); charsetName = contentAttributeValue.substring(start, end); } else /*if (metHttml5Charset) */ { charsetName = StringUtil.unquoteString(contentAttributeValue); } charsetNameRef.set(charsetName); terminate(); } if ("head".equals(name)) { terminate(); } inTag.remove(name); metHttpEquiv = false; metHtml5Charset = false; contentAttributeValue = null; } private String contentAttributeValue; @Override public void attribute(final CharSequence localName, final CharSequence v, final int startoffset, final int endoffset) { final @NonNls String name = StringUtil.toLowerCase(localName.toString()); if (inTag.contains("meta")) { @NonNls String value = StringUtil.toLowerCase(v.toString()); if (name.equals("http-equiv")) { metHttpEquiv |= value.equals("content-type"); } else if (name.equals(CHARSET)) { metHtml5Charset = true; contentAttributeValue = value; } if (name.equals("content")) { contentAttributeValue = value; } } } @Override public void textElement(final CharSequence display, final CharSequence physical, final int startoffset, final int endoffset) { } @Override public void entityRef(final CharSequence ref, final int startOffset, final int endOffset) { } @Override public void error(@NotNull String message, int startOffset, int endOffset) { } }); } catch (TerminateException ignored) { //ignore } catch (Exception ignored) { // some weird things can happen, like unbalanaced tree } return charsetNameRef.get(); }