XML实现与解析

xml.h

#pragma once
#include <iostream>
#include <map>
#include <list>
#include <string>
#include <sstream>
#include <functional>
namespace yazi
{
    namespace xml
    {
        class Vaule
        {
        public:
            Vaule();
            Vaule(bool vaule);
            Vaule(int vaule);
            Vaule(double vaule);
            Vaule(const char* vaule);
            Vaule(const std::string& vaule);
            ~Vaule();

            Vaule& operator=(bool vaule);
            Vaule& operator=(int vaule);
            Vaule& operator=(double vaule);
            Vaule& operator=(const char* vaule);
            Vaule& operator=(const std::string vaule);
            bool operator == (const Vaule& other);
            bool operator != (const Vaule& other);
            operator bool();
            operator int();
            operator double();
            operator std::string();
            std::string GetVaule()const { return _vaule; }
            // friend std::stringstream& operator<<(std::stringstream& ss, const Vaule& vaule);
        private:
            std::string _vaule;
        };

        class Xml
        {
        public:
            Xml();
            Xml(const char* name);
            Xml(const std::string& name);
            Xml(const Xml& other);
            Xml& operator=(const Xml& other);
            Xml(const std::string& node, const std::map<std::string, Vaule>& mp,const std::string text= "");
            //节点名称
            std::string name() const;
            void name(const std::string& name);

            //节点内容
            std::string text()const;
            void text(const std::string& text);

            //节点属性
            Vaule& attr(const std::string& key);
            void attr(const std::string& key, const Vaule& val);


            std::string str()const;

            void append(const Xml& child);
            Xml& operator[](int index);
            Xml& operator[](const char* name);
            Xml& operator[](const std::string name);


            void remove(int index);
            void remove(const char* name);
            void remove(const std::string& name);

            bool operator_file(const std::string& filename, std::function<void(Xml*)> func);

            void clear();

            typedef std::list<Xml>::iterator iterator;
            iterator begin()
            {
                return _child->begin();
            }
            iterator end()
            {
                return _child->end();
            }
            iterator erase(iterator it)
            {
                it->clear();
                return _child->erase(it);
            }
            int size() const
            {
                return static_cast<int> (_child->size());
            }
            bool load(const std::string& filename);
            bool save(const std::string& filename);
            bool parse(const std::string& str);
        private:
            std::string* _name;
            std::string* _text;
            std::map<std::string, Vaule>* _attrs;
            std::list<Xml>* _child;
        };
    }
}


    

xml.cc

#include "Xml.h"
#include "Parser.h"
using namespace yazi::xml;

Vaule::Vaule()
{
}
Vaule::Vaule(bool vaule)
{
    *this = vaule;
}
Vaule::Vaule(int vaule)
{
    *this = vaule;
}
Vaule::Vaule(double vaule)
{
    *this = vaule;
}
Vaule::Vaule(const char* vaule) : _vaule(vaule)
{
}
Vaule::Vaule(const std::string& vaule) : _vaule(vaule)
{
}
Vaule::~Vaule()
{
}

Vaule& Vaule::operator=(bool vaule)
{
    _vaule = vaule ? "true" : "false";
    return *this;
}
Vaule& Vaule::operator=(int vaule)
{
    std::stringstream ss;
    ss << vaule;
    _vaule = ss.str();
    return *this;
}
Vaule& Vaule::operator=(double vaule)
{
    std::stringstream ss;
    ss << vaule;
    _vaule = ss.str();
    return *this;
}
Vaule& Vaule::operator=(const char* vaule)
{
    _vaule = vaule;
    return *this;
}
Vaule& Vaule::operator=(const std::string vaule)
{
    _vaule = vaule;
    return *this;
}

bool Vaule::operator==(const Vaule& other)
{
    return _vaule == other._vaule;
}
bool Vaule::operator!=(const Vaule& other)
{
    return !((*this) == other);
}
Vaule::operator bool()
{
    if (_vaule == "true")
    {
        return true;
    }
    else if (_vaule == "false")
    {
        return false;
    }
    else
    {
        return false;
    }
}
Vaule::operator int()
{
    return std::atoi(_vaule.c_str());
}
Vaule::operator double()
{
    return std::atof(_vaule.c_str());
}
Vaule::operator std::string()
{
    return _vaule;
}

Xml::Xml() : _name(nullptr), _text(nullptr), _attrs(nullptr), _child(nullptr)
{
}
Xml::Xml(const char* name) : _name(nullptr), _text(nullptr), _attrs(nullptr), _child(nullptr)
{
    _name = new std::string(name);
}
Xml::Xml(const std::string& name) : _name(nullptr), _text(nullptr), _attrs(nullptr), _child(nullptr)
{
    _name = new std::string(name);
}
Xml::Xml(const Xml& other)
{
    _name = other._name;
    _text = other._text;
    _attrs = other._attrs;
    _child = other._child;
}
Xml::Xml(const std::string& node, const std::map<std::string, Vaule>& mp, const std::string text)
{
    _name = new std::string(node);
    _attrs = new std::map<std::string, Vaule>(mp);
    _text = new std::string(text);
}
Xml& Xml::operator=(const Xml& other)
{
    clear();
    _name = other._name;
    _text = other._text;
    _attrs = other._attrs;
    _child = other._child;
    return *this;
}

std::string Xml::name() const
{
    if (_name == nullptr)
    {
        return "";
    }
    return *_name;
}
void Xml::name(const std::string& name)
{
    if (_name != nullptr)
    {
        delete _name;
        _name = nullptr;
    }
    _name = new std::string(name);
}

std::string Xml::text() const
{
    if (_text == nullptr)
    {
        return "";
    }
    return *_text;
}
void Xml::text(const std::string& text)
{
    if (_text != nullptr)
    {
        delete _text;
        _text = nullptr;
    }
    _text = new std::string(text);
}

Vaule& Xml::attr(const std::string& key)
{
    if (_attrs == nullptr)
    {
        _attrs = new std::map<std::string, Vaule>();
    }
    return (*_attrs)[key];
}
void Xml::attr(const std::string& key, const Vaule& val)
{
    if (_attrs == nullptr)
    {
        _attrs = new std::map<std::string, Vaule>();
    }
    (*_attrs)[key] = val;
}
std::string Xml::str() const
{
    if (_name == nullptr)
    {
        // throw new std::logic_error("element name is empty");
        return "";
    }
    std::stringstream ss;
    ss << "<";
    ss << *_name;
    if (_attrs != nullptr)
    {
        for (const auto& e : (*_attrs))
        {
            ss << " " << e.first << "=\"" << e.second.GetVaule() << "\"";
        }
        /*for (auto it = _attrs->begin(); it != _attrs->end(); ++it)
        {
            ss << " " << it->first << "=\"" << it->second << "\"";
        }*/
    }
    ss << ">";
    if (_child != nullptr)
    {
        for (const auto& e : (*_child))
        {
            ss << e.str();
        }
    }
    if (_text != nullptr)
    {
        ss << *_text;
    }
    ss << "</" << *_name << ">";
    return ss.str();
}
void Xml::append(const Xml& child)
{
    if (_child == nullptr)
    {
        _child = new std::list<Xml>();
    }
    _child->push_back(child);
}
Xml& Xml::operator[](int index)
{
    if (index < 0)
    {
        throw new std::logic_error("index less than zero");
    }
    if (_child == nullptr)
    {
        _child = new std::list<Xml>();
    }
    int size = static_cast<int>(_child->size());
    if (index >= 0 && index < size)
    {
        auto it = _child->begin();
        for (int i = 0; i < size; ++i)
        {
            ++it;
        }
        return *it;
    }
    if (index >= size)
    {
        for (int i = index; i <= size; ++i)
        {
            _child->push_back(Xml());
        }
    }
    return _child->back();
}

Xml& Xml::operator[](const char* name)
{
    return (*this)[std::string(name)];
}
Xml& Xml::operator[](const std::string name)
{
    if (_child == nullptr)
    {
        _child = new std::list<Xml>();
    }
    // for(const auto &e : (*child))
    // {
    //     if(e.name() == name)
    //     {
    //
    //     }
    // }
    for (auto it = _child->begin(); it != _child->end(); ++it)
    {
        if (it->name() == name)
        {
            return *it;
        }
    }
    _child->push_back(Xml(name));
    return _child->back();
}
void Xml::remove(int index)
{
    if (_child == nullptr)
    {
        return;
    }
    int size = static_cast<int>(_child->size());
    if (index < 0 || index >= size)
    {
        return;
    }
    auto it = _child->begin();
    for (int i = 0; i < index; ++i)
    {
        it++;
    }
    it->clear();
    _child->erase(it);
}
void Xml::remove(const char* name)
{
    remove(std::string(name));
}
void Xml::remove(const std::string& name)
{
    if (_child == nullptr)
    {
        return;
    }
    for (auto it = _child->begin(); it != _child->end();)
    {
        if (it->name() == name)
        {
            it->clear();
            it = _child->erase(it);
        }
        else
        {
            ++it;
        }
    }
}
void Xml::clear()
{
    if (_name != nullptr)
    {
        delete _name;
        _name = nullptr;
    }
    if (_text != nullptr)
    {
        delete _text;
        _text = nullptr;
    }
    if (_attrs != nullptr)
    {
        delete _attrs;
        _attrs = nullptr;
    }
    if (_child != nullptr)
    {
        for (auto it = _child->begin(); it != _child->end(); ++it)
        {
            it->clear();
        }
        delete _child;
        _child = nullptr;
    }
}
bool Xml::load(const std::string& filename)
{
    Parser p;
    if (!p.load_file(filename))
    {
        return false;
    }
    *this = p.parse();
    return true;
}
bool Xml::save(const std::string& filename)
{
    std::ofstream out(filename);
    if (out.fail())
    {
        return false;
    }
    out << str();
    out.close();
    return true;
}
bool Xml::parse(const std::string& str)
{
    Parser p;
    if (!p.load_string(str))
    {
        return false;
    }
    *this = p.parse();
    return true;
}
bool Xml::operator_file(const std::string& filename, std::function<void(Xml*)> func)
{
    Parser p;
    if (!p.load_file(filename))
    {
        return false;
    }
    *this = p.parse();
    func(this);
    save(filename);
    return true;
}

Parser.h

#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include "Xml.h"
namespace yazi
{
    namespace xml
    {
        class Parser
        {
        public:
            Parser();
            bool load_file(const std::string & file);
            bool load_string(const std::string& str);
            Xml parse();
        private:
            void skip_white_space();  //去空格
            bool parse_declaration();
            bool parse_comment();
            Xml parse_element();
            std::string parse_element_name();
            std::string parse_element_text();
            std::string parse_element_attr_key();
            std::string parse_element_attr_val();
        private:
            std::string _str;
            int _index;
        };
    }
}

Parser.cc

#include "Parser.h"
using namespace yazi::xml;
Parser::Parser():_str(""),_index(0)
{

}
bool Parser::load_file(const std::string &file)
{
    std::ifstream in(file);
    if(in.fail())
    {
        return false;
    }
    std::stringstream ss;
    ss << in.rdbuf();
    _str = ss.str();
    _index = 0;
    in.close();
    return true;
}
bool Parser::load_string(const std::string &str)
{
    _str = str;
    _index = 0;
    return true;
}
Xml Parser::parse()
{
    //核心
    skip_white_space();
    //头
    if(_str.compare(_index,5,"<?xml") == 0)
    {
        if(!parse_declaration())
        {
            throw new std::logic_error("parse declaration error");
        }
    }
    skip_white_space();  
    //注释
    while(_str.compare(_index,4,"<!--") == 0)
    {
        if(!parse_comment())
        {
            throw new std::logic_error("parse comment error");
        }
        skip_white_space();
    }
    //解析根
    if(_str[_index] == '<' && (isalpha(_str[_index + 1]) || _str[_index + 1] == '_'))
    {
        return parse_element();
    }
    throw new std::logic_error("parse element error");
}

void Parser::skip_white_space() // 去空格
{
    while(_str[_index] == ' ' || _str[_index] == '\r' || _str[_index] == '\n' || _str[_index] == '\t')
    {
        ++_index;
    }
}
bool Parser::parse_declaration()
{
    if(_str.compare(_index,5,"<?xml") != 0)
    {
        return false;
    }
    _index += 5;
    size_t pos = _str.find("?>",_index);
    if(pos == std::string::npos)
    {
        return false;
    }
    _index = static_cast<int>(pos) + 2;
    return true;
}
bool Parser::parse_comment()
{
    if(_str.compare(_index,4,"<!--") != 0)
    {
        return false;
    }
    _index += 4;
    size_t pos = _str.find("-->",_index);
    if(pos == std::string::npos)
    {
        return false;
    }
    _index = static_cast<int> (pos) + 3;
    return true;
}
Xml Parser::parse_element()
{
    Xml elem;
    ++_index;
    skip_white_space();
    std::string name = parse_element_name();
    elem.name(std::move(name));
    while(_str[_index] != '\0')
    {
        skip_white_space();
        if(_str[_index] == '/')
        {
            if(_str[_index + 1] == '>')
            {
                _index += 2;
                break;
            }
            else
            {
                throw std::logic_error("xml empty element is error");
            }
        }
        else if(_str[_index] == '>')
        {
            ++_index;
            std::string text = parse_element_text();
            if(text != "")
            {
                elem.text(std::move(text));
            }
        }
        else if(_str[_index] == '<')
        {
            if(_str[_index + 1] == '/')
            {
                std::string end_tag = "</" + name + ">";
                size_t pos = _str.find(end_tag,_index);
                if(pos == std::string::npos)
                {
                    throw new std::logic_error("xml element" + name + "end tag not found");
                }
                _index = (pos + end_tag.size());
                break;
            }
            else if(_str.compare(_index,4,"<!--") == 0)
            {
                if(!parse_comment())
                {
                    throw new std::logic_error("xml coment is error");
                }
            }
            else
            {
                elem.append(parse_element());
            }
        }
        else
        {
            std::string key = parse_element_attr_key();
            skip_white_space();
            if(_str[_index] != '=')
            {
                throw new std::logic_error("xml emlment attr is error" + key);
            }
            ++_index;
            skip_white_space();
            std::string val = parse_element_attr_val();
            elem.attr(std::move(key),std::move(val));
        }
    }
    return elem;
}
std::string Parser::parse_element_name()
{
    int pos = _index;
    if(isalpha(_str[_index]) || _str[_index] == '_')
    {
        ++_index;
        while(isalnum(_str[_index]) \
             || _str[_index] == '_' \
             || _str[_index] == '-' \
             || _str[_index] == ':' \
             || _str[_index] == '.')
        {
            ++_index;
        }
    }
    return _str.substr(pos, _index - pos);
}
std::string Parser::parse_element_text()
{
    int pos = _index;
    while(_str[_index] != '<')
    {
        ++_index;
    }
    return _str.substr(pos,_index - pos);
}
std::string Parser::parse_element_attr_key()
{
    int pos = _index;
    if(isalpha(_str[_index]) || _str[_index] == '_')
    {
        ++_index;
        while(isalnum(_str[_index]) \
             || _str[_index] == '_' \
             || _str[_index] == '-' \
             || _str[_index] == ':')
        {
            ++_index;
        }
    }
    return _str.substr(pos, _index - pos);
}
std::string Parser::parse_element_attr_val()
{
    if(_str[_index] != '"')
    {
        throw new std::logic_error("xml element attr vaule should be in double quotes");
    }
    ++_index;
    int pos = _index;
    while(_str[_index] != '"')
    {
        ++_index;
    }
    ++_index;
    return _str.substr(pos,_index - pos - 1);
}

main.cc

#include "./xml/Xml.h"
using namespace yazi::xml;

#include <vector>
using namespace yazi::xml;
void write_file(Xml* x)
{
    Xml ip1("addr", { {"ip","192.168.0.0.1"},{"mac","AB-BI-B7-0C"} });
    (*x)["PLANT"]["AVAILABILITY"]["addr"].text("789");
    (*x)["PLANT"]["AVAILABILITY"].append(ip1);
    (*x)["PLANT"]["AVAILABILITY"].text("789");
   // (*x)["PLANT"]["AVAILABILITY"].text("1234");
   // x->remove("addr");
    //(*x)["PLANT"].remove("addr");
}
int main()
{

    Xml root;
    root.operator_file("./test_xml", write_file);
   /* Xml s1("student1");
    s1.attr("id", 100);
    s1.attr("name", "张三");
    s1.attr("class", "塔利班");
    s1.text("我是一个恐怖分子");
    


    Xml s2("student2");
    s2.attr("id", true);
    s2.attr("name", "李四");
    s2.attr("class", "塔利班");
    s2.text("我是一个恐怖分子");

    int falg = s1.attr("id");
    std::cout << falg << std::endl;
    Xml root;
    root.name("root");
    root[0] = s1;
    root[1] = s2;
    
    std::cout << root.str() << std::endl;
    root.clear();
    return 0;*/
    /*Xml root;
    root.name("root");
    Xml Ip("Ip");
  
    Xml ip1("addr", { { "ip", "192.168.0.0.1" }, { "mac","AB-BI-B7-0C" } });
    Xml ip2("addr", { { "ip", "192.168.0.0.2" }, { "mac","AB-BI-B7-0C" } });
    Xml ip3("addr", { { "ip", "192.168.0.0.3" }, { "mac","AB-BI-B7-0C" } });
    Xml ip4("addr", { { "ip", "192.168.0.0.4" }, { "mac","AB-BI-B7-0C" } });
    Xml ip5("addr", { { "ip", "192.168.0.0.5" }, { "mac","AB-BI-B7-0C" } });
    Ip.append(ip1);
    Ip.append(ip2);
    Ip.append(ip3);
    Ip.append(ip4);
    root.append(Ip);
    root["Ip"].append(ip5);
    std::cout << root.str() << std::endl;*/
  /*  Xml root;
    root.name("root");
    Xml ip1("addr", { {"ip","192.168.0.0.1"},{"mac","AB-BI-B7-0C"} });
    root.append(ip1);
    std::cout << root.str() << std::endl;
    root.clear();*/
 }

test_xml

<CATALOG><PLANT><COMMON>Bloodroot</COMMON><BOTANICAL>Sanguinaria canadensis</BOTANICAL><ZONE>4</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$2.44</PRICE><AVAILABILITY><addr>789</addr><addr ip="192.168.0.0.1" mac="AB-BI-B7-0C"></addr>789</AVAILABILITY>
        </PLANT><PLANT><COMMON>Columbine</COMMON><BOTANICAL>Aquilegia canadensis</BOTANICAL><ZONE>3</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$9.37</PRICE><AVAILABILITY>030699</AVAILABILITY>
        </PLANT><PLANT><COMMON>Marsh Marigold</COMMON><BOTANICAL>Caltha palustris</BOTANICAL><ZONE>4</ZONE><LIGHT>Mostly Sunny</LIGHT><PRICE>$6.81</PRICE><AVAILABILITY>051799</AVAILABILITY>
        </PLANT><PLANT><COMMON>Cowslip</COMMON><BOTANICAL>Caltha palustris</BOTANICAL><ZONE>4</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$9.90</PRICE><AVAILABILITY>030699</AVAILABILITY>
        </PLANT><PLANT><COMMON>Dutchman's-Breeches</COMMON><BOTANICAL>Dicentra cucullaria</BOTANICAL><ZONE>3</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$6.44</PRICE><AVAILABILITY>012099</AVAILABILITY>
        </PLANT><PLANT><COMMON>Ginger, Wild</COMMON><BOTANICAL>Asarum canadense</BOTANICAL><ZONE>3</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$9.03</PRICE><AVAILABILITY>041899</AVAILABILITY>
        </PLANT><PLANT><COMMON>Hepatica</COMMON><BOTANICAL>Hepatica americana</BOTANICAL><ZONE>4</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$4.45</PRICE><AVAILABILITY>012699</AVAILABILITY>
        </PLANT><PLANT><COMMON>Liverleaf</COMMON><BOTANICAL>Hepatica americana</BOTANICAL><ZONE>4</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$3.99</PRICE><AVAILABILITY>010299</AVAILABILITY>
        </PLANT><PLANT><COMMON>Jack-In-The-Pulpit</COMMON><BOTANICAL>Arisaema triphyllum</BOTANICAL><ZONE>4</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$3.23</PRICE><AVAILABILITY>020199</AVAILABILITY>
        </PLANT><PLANT><COMMON>Mayapple</COMMON><BOTANICAL>Podophyllum peltatum</BOTANICAL><ZONE>3</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$2.98</PRICE><AVAILABILITY>060599</AVAILABILITY>
        </PLANT><PLANT><COMMON>Phlox, Woodland</COMMON><BOTANICAL>Phlox divaricata</BOTANICAL><ZONE>3</ZONE><LIGHT>Sun or Shade</LIGHT><PRICE>$2.80</PRICE><AVAILABILITY>012299</AVAILABILITY>
        </PLANT><PLANT><COMMON>Phlox, Blue</COMMON><BOTANICAL>Phlox divaricata</BOTANICAL><ZONE>3</ZONE><LIGHT>Sun or Shade</LIGHT><PRICE>$5.59</PRICE><AVAILABILITY>021699</AVAILABILITY>
        </PLANT><PLANT><COMMON>Spring-Beauty</COMMON><BOTANICAL>Claytonia Virginica</BOTANICAL><ZONE>7</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$6.59</PRICE><AVAILABILITY>020199</AVAILABILITY>
        </PLANT><PLANT><COMMON>Trillium</COMMON><BOTANICAL>Trillium grandiflorum</BOTANICAL><ZONE>5</ZONE><LIGHT>Sun or Shade</LIGHT><PRICE>$3.90</PRICE><AVAILABILITY>042999</AVAILABILITY>
        </PLANT><PLANT><COMMON>Wake Robin</COMMON><BOTANICAL>Trillium grandiflorum</BOTANICAL><ZONE>5</ZONE><LIGHT>Sun or Shade</LIGHT><PRICE>$3.20</PRICE><AVAILABILITY>022199</AVAILABILITY>
        </PLANT><PLANT><COMMON>Violet, Dog-Tooth</COMMON><BOTANICAL>Erythronium americanum</BOTANICAL><ZONE>4</ZONE><LIGHT>Shade</LIGHT><PRICE>$9.04</PRICE><AVAILABILITY>020199</AVAILABILITY>
        </PLANT><PLANT><COMMON>Trout Lily</COMMON><BOTANICAL>Erythronium americanum</BOTANICAL><ZONE>4</ZONE><LIGHT>Shade</LIGHT><PRICE>$6.94</PRICE><AVAILABILITY>032499</AVAILABILITY>
        </PLANT><PLANT><COMMON>Adder's-Tongue</COMMON><BOTANICAL>Erythronium americanum</BOTANICAL><ZONE>4</ZONE><LIGHT>Shade</LIGHT><PRICE>$9.58</PRICE><AVAILABILITY>041399</AVAILABILITY>
        </PLANT><PLANT><COMMON>Anemone</COMMON><BOTANICAL>Anemone blanda</BOTANICAL><ZONE>6</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$8.86</PRICE><AVAILABILITY>122698</AVAILABILITY>
        </PLANT><PLANT><COMMON>Grecian Windflower</COMMON><BOTANICAL>Anemone blanda</BOTANICAL><ZONE>6</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$9.16</PRICE><AVAILABILITY>071099</AVAILABILITY>
        </PLANT><PLANT><COMMON>Bee Balm</COMMON><BOTANICAL>Monarda didyma</BOTANICAL><ZONE>4</ZONE><LIGHT>Shade</LIGHT><PRICE>$4.59</PRICE><AVAILABILITY>050399</AVAILABILITY>
        </PLANT><PLANT><COMMON>Bergamot</COMMON><BOTANICAL>Monarda didyma</BOTANICAL><ZONE>4</ZONE><LIGHT>Shade</LIGHT><PRICE>$7.16</PRICE><AVAILABILITY>042799</AVAILABILITY>
        </PLANT><PLANT><COMMON>Black-Eyed Susan</COMMON><BOTANICAL>Rudbeckia hirta</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Sunny</LIGHT><PRICE>$9.80</PRICE><AVAILABILITY>061899</AVAILABILITY>
        </PLANT><PLANT><COMMON>Buttercup</COMMON><BOTANICAL>Ranunculus</BOTANICAL><ZONE>4</ZONE><LIGHT>Shade</LIGHT><PRICE>$2.57</PRICE><AVAILABILITY>061099</AVAILABILITY>
        </PLANT><PLANT><COMMON>Crowfoot</COMMON><BOTANICAL>Ranunculus</BOTANICAL><ZONE>4</ZONE><LIGHT>Shade</LIGHT><PRICE>$9.34</PRICE><AVAILABILITY>040399</AVAILABILITY>
        </PLANT><PLANT><COMMON>Butterfly Weed</COMMON><BOTANICAL>Asclepias tuberosa</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Sunny</LIGHT><PRICE>$2.78</PRICE><AVAILABILITY>063099</AVAILABILITY>
        </PLANT><PLANT><COMMON>Cinquefoil</COMMON><BOTANICAL>Potentilla</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Shade</LIGHT><PRICE>$7.06</PRICE><AVAILABILITY>052599</AVAILABILITY>
        </PLANT><PLANT><COMMON>Primrose</COMMON><BOTANICAL>Oenothera</BOTANICAL><ZONE>3 - 5</ZONE><LIGHT>Sunny</LIGHT><PRICE>$6.56</PRICE><AVAILABILITY>013099</AVAILABILITY>
        </PLANT><PLANT><COMMON>Gentian</COMMON><BOTANICAL>Gentiana</BOTANICAL><ZONE>4</ZONE><LIGHT>Sun or Shade</LIGHT><PRICE>$7.81</PRICE><AVAILABILITY>051899</AVAILABILITY>
        </PLANT><PLANT><COMMON>Blue Gentian</COMMON><BOTANICAL>Gentiana</BOTANICAL><ZONE>4</ZONE><LIGHT>Sun or Shade</LIGHT><PRICE>$8.56</PRICE><AVAILABILITY>050299</AVAILABILITY>
        </PLANT><PLANT><COMMON>Jacob's Ladder</COMMON><BOTANICAL>Polemonium caeruleum</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Shade</LIGHT><PRICE>$9.26</PRICE><AVAILABILITY>022199</AVAILABILITY>
        </PLANT><PLANT><COMMON>Greek Valerian</COMMON><BOTANICAL>Polemonium caeruleum</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Shade</LIGHT><PRICE>$4.36</PRICE><AVAILABILITY>071499</AVAILABILITY>
        </PLANT><PLANT><COMMON>California Poppy</COMMON><BOTANICAL>Eschscholzia californica</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Sun</LIGHT><PRICE>$7.89</PRICE><AVAILABILITY>032799</AVAILABILITY>
        </PLANT><PLANT><COMMON>Shooting Star</COMMON><BOTANICAL>Dodecatheon</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Mostly Shady</LIGHT><PRICE>$8.60</PRICE><AVAILABILITY>051399</AVAILABILITY>
        </PLANT><PLANT><COMMON>Snakeroot</COMMON><BOTANICAL>Cimicifuga</BOTANICAL><ZONE>Annual</ZONE><LIGHT>Shade</LIGHT><PRICE>$5.63</PRICE><AVAILABILITY>071199</AVAILABILITY>
        </PLANT><PLANT><COMMON>Cardinal Flower</COMMON><BOTANICAL>Lobelia cardinalis</BOTANICAL><ZONE>2</ZONE><LIGHT>Shade</LIGHT><PRICE>$3.02</PRICE><AVAILABILITY>022299</AVAILABILITY>
        </PLANT>
    </CATALOG>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值