Обрезать строку до длины, игнорируя HTML - PullRequest
9 голосов
/ 10 апреля 2009

Эта проблема сложная. Наше приложение позволяет пользователям размещать новости на главной странице. Эти новости вводятся с помощью текстового редактора, который позволяет HTML. На главной странице мы хотим отображать только усеченную сводку новостей.

Например, вот полный текст, который мы отображаем, включая HTML


В попытке освободить место в офисе, на кухне, я вытащил все случайные кружки и поставил их на стол в столовой. Если вы не уверены в том, что владеете этой кружкой Cheyenne Courier с 1992 года или, возможно, кружкой BC Tel Advanced Communications с 1997 года, они будут помещены в коробку и подарены офису, который больше нуждается в кружках, чем мы. *

Мы хотим урезать новость до 250 символов, но исключить HTML.

Метод, который мы используем для обрезки, в настоящее время включает в себя HTML, и это приводит к значительному усечению некоторых новостных сообщений, которые имеют большой объем HTML.

Например, если приведенный выше пример включает в себя тонны HTML, он может выглядеть следующим образом:

В попытке освободить немного места в офисе, на кухне я вытащил ...

Это не то, что мы хотим.

Есть ли у кого-нибудь способ маркировки HTML-тегов, чтобы сохранить положение в строке, выполнить проверку длины и / или обрезку строки и восстановить HTML-код внутри строки в ее старом местоположении?

Ответы [ 7 ]

10 голосов
/ 10 апреля 2009

Начните с первого символа поста, переступая через каждый символ. Каждый раз, когда вы пересекаете персонажа, увеличивайте счетчик. Когда вы найдете символ «<», прекратите увеличивать счетчик, пока не нажмете «>». Ваша позиция, когда счетчик достигает 250, - это то место, где вы действительно хотите отрезать.

Обратите внимание, что это будет иметь другую проблему, с которой вам придется столкнуться, когда HTML-тег открывается, но не закрывается до обрезания.

2 голосов
/ 02 мая 2012

Следуя предложению для конечного компьютера с двумя состояниями, я только что разработал для этой цели простой анализатор HTML в Java:

http://pastebin.com/jCRqiwNH

и вот контрольный пример:

http://pastebin.com/37gCS4tV

А вот код Java:

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class HtmlShortener {

    private static final String TAGS_TO_SKIP = "br,hr,img,link";
    private static final String[] tagsToSkip = TAGS_TO_SKIP.split(",");
    private static final int STATUS_READY = 0;

        private int cutPoint = -1;
    private String htmlString = "";

    final List<String> tags = new LinkedList<String>();

    StringBuilder sb = new StringBuilder("");
    StringBuilder tagSb = new StringBuilder("");

    int charCount = 0;
    int status = STATUS_READY;

    public HtmlShortener(String htmlString, int cutPoint){
        this.cutPoint = cutPoint;
        this.htmlString = htmlString;
    }

    public String cut(){

        // reset 
        tags.clear();
        sb = new StringBuilder("");
        tagSb = new StringBuilder("");
        charCount = 0;
        status = STATUS_READY;

        String tag = "";

        if (cutPoint < 0){
            return htmlString;
        }

        if (null != htmlString){

            if (cutPoint == 0){
                return "";
            }

            for (int i = 0; i < htmlString.length(); i++){

                String strC = htmlString.substring(i, i+1);


                if (strC.equals("<")){

                    // new tag or tag closure

                    // previous tag reset
                    tagSb = new StringBuilder("");
                    tag = "";

                    // find tag type and name
                    for (int k = i; k < htmlString.length(); k++){

                        String tagC = htmlString.substring(k, k+1);
                        tagSb.append(tagC);

                        if (tagC.equals(">")){
                            tag = getTag(tagSb.toString());
                            if (tag.startsWith("/")){

                                // closure
                                if (!isToSkip(tag)){
                                    sb.append("</").append(tags.get(tags.size() - 1)).append(">");
                                    tags.remove((tags.size() - 1));
                                }

                            } else {

                                // new tag
                                sb.append(tagSb.toString());

                                if (!isToSkip(tag)){
                                    tags.add(tag);  
                                }

                            }

                            i = k;
                            break;
                        }

                    }

                } else {

                    sb.append(strC);
                    charCount++;

                }

                // cut check
                if (charCount >= cutPoint){

                    // close previously open tags
                    Collections.reverse(tags);
                    for (String t : tags){
                        sb.append("</").append(t).append(">");
                    }
                    break;
                } 

            }

            return sb.toString();

        } else {
            return null;
        }

    }

    private boolean isToSkip(String tag) {

        if (tag.startsWith("/")){
            tag = tag.substring(1, tag.length());
        }

        for (String tagToSkip : tagsToSkip){
            if (tagToSkip.equals(tag)){
                return true;
            }
        }

        return false;
    }

    private String getTag(String tagString) {

        if (tagString.contains(" ")){
            // tag with attributes
            return tagString.substring(tagString.indexOf("<") + 1, tagString.indexOf(" "));
        } else {
            // simple tag
            return tagString.substring(tagString.indexOf("<") + 1, tagString.indexOf(">"));
        }


    }

}

0 голосов
/ 04 августа 2014

Вы можете попробовать следующий пакет npm

подрезать-HTML

Он обрезает достаточное количество текста внутри HTML-тегов, сохраняет исходную HTML-структуру, удаляет HTML-теги после достижения лимита и закрывает открытые теги.

0 голосов
/ 04 сентября 2009

Я знаю, что это немного позже опубликованной даты, но у меня была похожая проблема, и именно так я и решил ее. Меня беспокоит скорость регулярных выражений по сравнению с целыми числами в массиве.

Также, если у вас есть пробел перед html-тегом, и после этого это не исправляется

private string HtmlTrimmer(string input, int len)
{
    if (string.IsNullOrEmpty(input))
        return string.Empty;
    if (input.Length <= len)
        return input;

    // this is necissary because regex "^"  applies to the start of the string, not where you tell it to start from
    string inputCopy;
    string tag;

    string result = "";
    int strLen = 0;
    int strMarker = 0;
    int inputLength = input.Length;     

    Stack stack = new Stack(10);
    Regex text = new Regex("^[^<&]+");                
    Regex singleUseTag = new Regex("^<[^>]*?/>");            
    Regex specChar = new Regex("^&[^;]*?;");
    Regex htmlTag = new Regex("^<.*?>");

    while (strLen < len)
    {
        inputCopy = input.Substring(strMarker);
        //If the marker is at the end of the string OR 
        //the sum of the remaining characters and those analyzed is less then the maxlength
        if (strMarker >= inputLength || (inputLength - strMarker) + strLen < len)
            break;

        //Match regular text
        result += text.Match(inputCopy,0,len-strLen);
        strLen += result.Length - strMarker;
        strMarker = result.Length;

        inputCopy = input.Substring(strMarker);
        if (singleUseTag.IsMatch(inputCopy))
            result += singleUseTag.Match(inputCopy);
        else if (specChar.IsMatch(inputCopy))
        {
            //think of &nbsp; as 1 character instead of 5
            result += specChar.Match(inputCopy);
            ++strLen;
        }
        else if (htmlTag.IsMatch(inputCopy))
        {
            tag = htmlTag.Match(inputCopy).ToString();
            //This only works if this is valid Markup...
            if(tag[1]=='/')         //Closing tag
                stack.Pop();
            else                    //not a closing tag
                stack.Push(tag);
            result += tag;
        }
        else    //Bad syntax
            result += input[strMarker];

        strMarker = result.Length;
    }

    while (stack.Count > 0)
    {
        tag = stack.Pop().ToString();
        result += tag.Insert(1, "/");
    }
    if (strLen == len)
        result += "...";
    return result;
}
0 голосов
/ 10 апреля 2009

Вот реализация, которую я придумал, в C #:

public static string TrimToLength(string input, int length)
{
  if (string.IsNullOrEmpty(input))
    return string.Empty;

  if (input.Length <= length)
    return input;

  bool inTag = false;
  int targetLength = 0;

  for (int i = 0; i < input.Length; i++)
  {
    char c = input[i];

    if (c == '>')
    {
      inTag = false;
      continue;
    }

    if (c == '<')
    {
      inTag = true;
      continue;
    }

    if (inTag || char.IsWhiteSpace(c))
    {
      continue;
    }

    targetLength++;

    if (targetLength == length)
    {
      return ConvertToXhtml(input.Substring(0, i + 1));
    }
  }

  return input;
}

И несколько юнит-тестов, которые я использовал через TDD:

[Test]
public void Html_TrimReturnsEmptyStringWhenNullPassed()
{
  Assert.That(Html.TrimToLength(null, 1000), Is.Empty);
}

[Test]
public void Html_TrimReturnsEmptyStringWhenEmptyPassed()
{
  Assert.That(Html.TrimToLength(string.Empty, 1000), Is.Empty);
}

[Test]
public void Html_TrimReturnsUnmodifiedStringWhenSameAsLength()
{
  string source = "<div lang=\"en\" class=\"textBody localizable\" id=\"pageBody_en\">" +
                  "<img photoid=\"4041\" src=\"http://xxxxxxxx/imagethumb/562103830000/4041/300x300/False/mugs.jpg\" style=\"float: right;\" class=\"photoRight\" alt=\"\"/>" +
                  "<br/>" +
                  "In an attempt to make a bit more space in the office, kitchen, I";

  Assert.That(Html.TrimToLength(source, 250), Is.EqualTo(source));
}

[Test]
public void Html_TrimWellFormedHtml()
{
  string source = "<div lang=\"en\" class=\"textBody localizable\" id=\"pageBody_en\">" +
             "<img photoid=\"4041\" src=\"http://xxxxxxxx/imagethumb/562103830000/4041/300x300/False/mugs.jpg\" style=\"float: right;\" class=\"photoRight\" alt=\"\"/>" +
             "<br/>" +
             "In an attempt to make a bit more space in the office, kitchen, I've pulled out all of the random mugs and put them onto the lunch room table. Unless you feel strongly about the ownership of that Cheyenne Courier mug from 1992 or perhaps that BC Tel Advanced Communications mug from 1997, they will be put in a box and donated to an office in more need of mugs than us. <br/><br/>" +
             "In the meantime we have a nice selection of white Ikea mugs, some random Starbucks mugs, and others that have made their way into the office over the years. Hopefully that will suffice. <br/><br/>" +
             "</div>";

  string expected = "<div lang=\"en\" class=\"textBody localizable\" id=\"pageBody_en\">" +
                    "<img photoid=\"4041\" src=\"http://xxxxxxxx/imagethumb/562103830000/4041/300x300/False/mugs.jpg\" style=\"float: right;\" class=\"photoRight\" alt=\"\"/>" +
                    "<br/>" +
                    "In an attempt to make a bit more space in the office, kitchen, I've pulled out all of the random mugs and put them onto the lunch room table. Unless you feel strongly about the ownership of that Cheyenne Courier mug from 1992 or perhaps that BC Tel Advanced Communications mug from 1997, they will be put in";

  Assert.That(Html.TrimToLength(source, 250), Is.EqualTo(expected));
}

[Test]
public void Html_TrimMalformedHtml()
{
  string malformedHtml = "<div lang=\"en\" class=\"textBody localizable\" id=\"pageBody_en\">" +
                         "<img photoid=\"4041\" src=\"http://xxxxxxxx/imagethumb/562103830000/4041/300x300/False/mugs.jpg\" style=\"float: right;\" class=\"photoRight\" alt=\"\"/>" +
                         "<br/>" +
                         "In an attempt to make a bit more space in the office, kitchen, I've pulled out all of the random mugs and put them onto the lunch room table. Unless you feel strongly about the ownership of that Cheyenne Courier mug from 1992 or perhaps that BC Tel Advanced Communications mug from 1997, they will be put in a box and donated to an office in more need of mugs than us. <br/><br/>" +
                         "In the meantime we have a nice selection of white Ikea mugs, some random Starbucks mugs, and others that have made their way into the office over the years. Hopefully that will suffice. <br/><br/>";

  string expected = "<div lang=\"en\" class=\"textBody localizable\" id=\"pageBody_en\">" +
              "<img photoid=\"4041\" src=\"http://xxxxxxxx/imagethumb/562103830000/4041/300x300/False/mugs.jpg\" style=\"float: right;\" class=\"photoRight\" alt=\"\"/>" +
              "<br/>" +
              "In an attempt to make a bit more space in the office, kitchen, I've pulled out all of the random mugs and put them onto the lunch room table. Unless you feel strongly about the ownership of that Cheyenne Courier mug from 1992 or perhaps that BC Tel Advanced Communications mug from 1997, they will be put in";

  Assert.That(Html.TrimToLength(malformedHtml, 250), Is.EqualTo(expected));
}
0 голосов
/ 10 апреля 2009

Если я правильно понимаю проблему, вы хотите сохранить форматирование HTML, но не хотите считать его частью длины строки, которую вы храните.

Это можно сделать с помощью кода, который реализует простой конечный автомат .

2 состояния: InTag, OutOfTag
InTag:
- Переходит к OutOfTag, если встречается символ >
- Идет к себе любой другой встреченный персонаж
OutOfTag:
- Переходит к InTag, если встречается символ <
- Идет к себе любой другой встреченный персонаж

Ваше начальное состояние будет OutOfTag.

Вы реализуете конечный автомат, обрабатывая 1 символ за раз. Обработка каждого персонажа приводит вас в новое состояние.

Когда вы проводите свой текст через конечный автомат, вы также хотите сохранить выходной буфер и длину, с которой он встречался, доступной (чтобы вы знали, когда остановиться).

  1. Увеличивайте переменную длины каждый раз, когда вы находитесь в состоянии OutOfTag и обрабатываете другого символа. Вы можете при желании не увеличивать эту переменную, если у вас есть пробельный символ.
  2. Вы заканчиваете алгоритм, когда у вас больше нет символов или у вас есть желаемая длина, упомянутая в # 1.
  3. В выходной буфер включайте символы, с которыми вы сталкиваетесь, вплоть до длины, указанной в # 1.
  4. Храните стопку незакрытых тегов. Когда вы достигнете длины, для каждого элемента в стеке добавьте конечный тег. Проходя по алгоритму, вы можете узнать, когда встретите тег, сохранив переменную current_tag. Эта переменная current_tag запускается при входе в состояние InTag и заканчивается при входе в состояние OutOfTag (или когда встречается символ пробела в состоянии InTag). Если у вас есть стартовый тег, вы кладете его в стек. Если у вас есть конечный тег, вы извлекаете его из стека.
0 голосов
/ 10 апреля 2009

Не будет ли самый быстрый способ использовать метод jQuery text()?

Например:

<ul>
  <li>One</li>
  <li>Two</li>
  <li>Three</li>
</ul>

var text = $('ul').text();

Даст значение OneTwoThree в переменной text. Это позволит вам получить фактическую длину текста без HTML-кода.

...